agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning 50+ messages / 2 participants [nested] [flat]
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------19C79A137EF2D0DB3BE72F79 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1473 ++++++++++++++++++++++++------------------------- 1 file changed, 722 insertions(+), 751 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index d1e915c11a..2cd75a9673 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,123 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + <productname>PostgreSQL</productname> offers built-in support for the + following forms of partitioning: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + + If your application needs to use other forms of partitioning not listed + above, alternative methods such as inheritance and + <literal>UNION ALL</literal> views can be used instead. Such methods + offer flexibility but do not have some of the performance benefits + of built-in declarative partitioning. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,74 +2899,72 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> - Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + Partitions may themselves be defined as partitioned tables, referred to as + <firstterm>sub-partitioning</firstterm>. Partitions may have their own + indexes, constraints and default values, distinct from other partitions. + They do not inherit indexes from the partitioned table. See + <xref linkend="sql-createtable"> for more details on creating partitioned + tables and partitions. </para> <para> - Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see - <xref linkend="sql-altertable"> to learn more about the - <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. + It is not possible to turn a regular table into a partitioned table or + vice versa. However, it is possible to add a regular or partitioned table + containing data as a partition of a partitioned table, or remove a + partition from a partitioned table turning it into a standalone table; + see <xref linkend="sql-altertable"> to learn more about the + <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> + sub-commands. </para> <para> Individual partitions are linked to the partitioned table with inheritance - behind-the-scenes, however it is not possible to use some of the inheritance - features discussed in the previous section with partitioned tables and - partitions. For example, partitions cannot have any other parents than - the partitioned table it is a partition of, nor can a regular table inherit - from a partitioned table making the latter its parent. That means - partitioned table and partitions do not participate in inheritance with - regular tables. Since a partition hierarchy consisting of the - partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + behind-the-scenes, however it is not possible to use some of the + inheritance features discussed in the previous section with partitioned + tables and partitions. For example, a partition cannot have any parents + other than the partitioned table it is a partition of, nor can a regular + table inherit from a partitioned table making the latter its parent. + That means partitioned table and partitions do not participate in + inheritance with regular tables. Since a partition hierarchy consisting + of the partitioned table and its partitions is still an inheritance + hierarchy, all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> <para> Both <literal>CHECK</literal> and <literal>NOT NULL</literal> constraints of a partitioned table are always inherited by all its - partitions. There cannot be any <literal>CHECK</literal> constraints - that are marked <literal>NO INHERIT</literal>. + partitions. <literal>CHECK</literal> constraints that are marked + <literal>NO INHERIT</literal> are not allowed. </para> </listitem> <listitem> <para> The <literal>ONLY</literal> notation used to exclude child tables - would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + will cause an error for partitioned tables in the case of + schema-modifying commands such as most <literal>ALTER TABLE</literal> + commands. For example, dropping a column from only the parent does + not make sense for partitioned tables. </para> </listitem> <listitem> <para> - Partitions cannot have columns that are not present in the parent. - It is neither possible to specify columns when creating partitions - with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + Partitions cannot have columns that are not present in the parent. It + is neither possible to specify columns when creating partitions with + <command>CREATE TABLE</> nor is it possible to add columns to + partitions after-the-fact using <command>ALTER TABLE</>. Tables may be + added as a partition with <command>ALTER TABLE ... ATTACH PARTITION</> + only if their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,487 +2978,505 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), - although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), + although certain limitations exist in their usage. For example, data + inserted into the partitioned table is not routed to foreign table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> in this + case) and the list of column(s) to use as the partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + You may decide to use multiple columns in the partition key for range + partitioning if it's known that each of the selected columns will + divide the incoming data using successively more granular partition + criteria. Whereas using fewer columns may lead to coarser-grained + partitioning causing each partition to accept bigger set of data than + might be desirable. A query accessing the partitioned table will have + to scan fewer partitions if the conditions involve some or all of these + columns. For example, consider a table range partitioned using columns + <structfield>lastname</> and <structfield>firstname</> (in that order) + as the partition key. </para> - </listitem> - <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into the parent table that does not map + to one of the existing partitions will cause an error; appropriate + partition must be added manually. </para> - </listitem> - </itemizedlist> - </para> - - <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. - </para> - - </sect1> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> - - <indexterm> - <primary>partitioning</primary> - </indexterm> - - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible to specify + tablespace, storage parameters for each partition separately. + </para> - <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> + </para> - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> (or data that is directly inserted + into <structname>measurement_y2006m02</>, provided it satisfies its + partition constraint) will be further redirected to one of its + partitions based on the <structfield>peaktemp</> column. Partition + key specified may overlap with the parent's partition key, although + care must be taken when specifying the bounds of a sub-partition + such that the set of data it accepts constitutes a subset of what + the partition's own bounds allows; the system does not try to check + if that's really the case. + </para> + </listitem> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + Create an index on the key column(s), as well as any other indexes you + might want for every partition. - <varlistentry> - <term>Using Partitioned Tables</term> +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> + </para> + </listitem> <listitem> <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> - </varlistentry> - </variablelist> + </orderedlist> </para> <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: - - <variablelist> - <varlistentry> - <term>Range Partitioning</term> - - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term>List Partitioning</term> - - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - </sect2> + </sect3> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> <para> - To set up a partitioned table using inheritance, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. - </para> - <para> - This table will contain no data. Do not define any check - constraints on this table, unless you intend them to - be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. - </para> - </listitem> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <listitem> - <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> + <para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> + + <para> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <para> - Typical examples would be: <programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID >= 100 AND outletID < 200 ) +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; </programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: + + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: + <programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; + +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work + +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); </programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> + </para> - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may then drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </sect3> - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> + <para> + The following limitations apply to partitioned tables: + <itemizedlist> + <listitem> + <para> + It is not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. This also means that there is no way to create a primary + key, unique constraint, or exclusion constraint spanning all + partitions; it is only possible to constrain each leaf partition + individually. + </para> + </listitem> - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, foreign + keys referencing partitioned tables are not supported, nor are foreign + key references from a partitioned table to some other table. + </para> + </listitem> + + <listitem> + <para> + Using the <literal>ON CONFLICT</literal> clause with partitioned tables + will cause an error if <literal>DO UPDATE</literal> is specified as the + alternative action, because unique or exclusion constraints can only be + created on individual partitions. There is no support for enforcing + uniqueness (or an exclusion constraint) across an entire partitioning + hierarchy. + </para> + </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> - </orderedlist> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, + not the partitioned table as it is not supported. + </para> + </listitem> + </itemizedlist> </para> + </sect3> + </sect2> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> + While the built-in declarative partitioning is suitable for most + common use cases, there are some circumstances where a more flexible + approach may be useful. Partitioning can be implemented using table + inheritance, which allows for several features which are not supported + by declarative partitioning, such as: + + <itemizedlist> <listitem> <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. + Partitioning enforces a rule that all partitions must have exactly + the same set of columns as the parent, but table inheritance allows + children to have extra columns not present in the parent. </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> </listitem> <listitem> <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. + Table inheritance allows for multiple inheritance. </para> </listitem> <listitem> <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. + Declarative partitioning only supports list and range partitioning, + whereas table inheritance allows data to be divided in a manner of + the user's choosing. (Note, however, that if constraint exclusion is + unable to prune partitions effectively, query performance will be very + poor.) </para> </listitem> <listitem> <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. + Some operations require a stronger lock when using declarative + partitioning than when using table inheritance. For example, adding + or removing a partition to or from a partitioned table requires taking + an <literal>ACCESS EXCLUSIVE</literal> lock on the parent table, + whereas a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is enough + in the case of regular inheritance. </para> </listitem> - - </orderedlist> + </itemizedlist> </para> - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> + <para> + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, use + the following steps: - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> + <orderedlist spacing="compact"> + <listitem> + <para> + Create the <quote>master</quote> table, from which all of the + partitions will inherit. This table will contain no data. Do not + define any check constraints on this table, unless you intend them + to be applied equally to all partitions. There is no point in + defining any indexes or unique constraints on it, either. For our + example, master table is the <structname>measurement</structname> + table as originally defined. + </para> + </listitem> - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> + <listitem> + <para> + Create several <quote>child</quote> tables that each inherit from + the master table. Normally, these tables will not add any columns + to the set inherited from the master. + </para> - <listitem> - <para> - Next we create one partition for each active month: + <para> + We will refer to the child tables as partitions, though they are + in every way normal <productname>PostgreSQL</> tables (or, possibly, + foreign tables). + </para> + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); </programlisting> + </para> + </listitem> - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> + <listitem> + <para> + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. + </para> - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> + <para> + Typical examples would be: +<programlisting> +CHECK ( x = 1 ) +CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) +CHECK ( outletID >= 100 AND outletID < 200 ) +</programlisting> + Ensure that the constraints guarantee that there is no overlap + between the key values permitted in different partitions. A common + mistake is to set up range constraints like: +<programlisting> +CHECK ( outletID BETWEEN 100 AND 200 ) +CHECK ( outletID BETWEEN 200 AND 300 ) +</programlisting> + This is wrong since it is not clear which partition the key value + 200 belongs in. + </para> - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + <para> + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> - </para> - </listitem> + </para> - <listitem> - <para> - We probably need indexes on the key columns too: + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> + </listitem> + <listitem> + <para> + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> + </para> + </listitem> - We choose not to add further indexes at this time. - </para> - </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: + <listitem> + <para> + We want our application to be able to say <literal>INSERT INTO + measurement ...</> and have the data be redirected into the + appropriate partition table. We can arrange that by attaching + a suitable trigger function to the master table. + If data will be added only to the latest partition, we can + use a very simple trigger function: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3363,9 +3488,11 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> - After creating the function, we create a trigger which - calls the trigger function: + <para> + After creating the function, we create a trigger which + calls the trigger function: <programlisting> CREATE TRIGGER insert_measurement_trigger @@ -3373,15 +3500,15 @@ CREATE TRIGGER insert_measurement_trigger FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); </programlisting> - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> + We must redefine the trigger function each month so that it always + points to the current partition. The trigger definition does + not need to be updated, however. + </para> - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: + <para> + We might want to insert data and have the server automatically + locate the partition into which the row should be added. We + could do this with a more complex trigger function, for example: <programlisting> CREATE OR REPLACE FUNCTION measurement_insert_trigger() @@ -3393,183 +3520,120 @@ BEGIN ELSIF ( NEW.logdate >= DATE '2006-03-01' AND NEW.logdate < DATE '2006-04-01' ) THEN INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate >= DATE '2008-01-01' AND - NEW.logdate < DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - Create the <structname>measurement</> table as a partitioned table: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); + ... + ELSIF ( NEW.logdate >= DATE '2008-01-01' AND + NEW.logdate < DATE '2008-02-01' ) THEN + INSERT INTO measurement_y2008m01 VALUES (NEW.*); + ELSE + RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; + END IF; + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; </programlisting> - </para> - </listitem> - <listitem> - <para> - Then create partitions as follows: + The trigger definition is the same as before. + Note that each <literal>IF</literal> test must exactly match the + <literal>CHECK</literal> constraint for its partition. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> - </para> - </listitem> + <para> + While this function is more complex than the single-month case, + it doesn't need to be updated as often, since branches can be + added in advance of being needed. + </para> - <listitem> - <para> - Create indexes on the key columns just like in case of inheritance - partitions. - </para> - </listitem> - </orderedlist> + <note> + <para> + In practice it might be best to check the newest partition first, + if most inserts go into that partition. For simplicity we have + shown the trigger's tests in the same order as in other parts + of this example. + </para> + </note> - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: + <para> + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> - </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> + A rule has significantly more overhead than a trigger, but the + overhead is paid once per query rather than once per row, so this + method might be advantageous for bulk-insert situations. In most + cases, however, the trigger method will offer better performance. + </para> - </sect2> + <para> + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. + </para> + </listitem> - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> + </para> - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + <para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> - - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + </para> - When using a partitioned table: + <para> + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> + </para> - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. - </para> - - <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + <para> + To add a new partition to handle new data, create an empty partition + just as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,17 +3641,9 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may want to create the new table outside the partition + structure, and make it a partition after the data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 @@ -3598,31 +3654,64 @@ ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 -- possibly some other data preparation work ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> - The last of the above commands when using a partitioned table would be: + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> + + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> - <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. - </para> - </tip> - </sect2> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3721,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,160 +3805,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - Using the <literal>ON CONFLICT</literal> clause with partitioned tables - will cause an error if <literal>DO UPDATE</literal> is specified as the - alternative action, because unique or exclusion constraints can only be - created on individual partitions. There is no support for enforcing - uniqueness (or an exclusion constraint) across an entire partitioning - hierarchy. - </para> - </listitem> - - </itemizedlist> + Constraint exclusion is also used for declarative partitioning, however + it is not required to create <literal>CHECK</literal> constraints for + individual partitions as when using table inheritance. </para> <para> - The following caveats apply to constraint exclusion, which is currently - used by both inheritance and partitioned tables: + The following caveats apply to constraint exclusion, which is used by + both inheritance and partitioned tables: <itemizedlist> <listitem> @@ -3909,6 +3854,32 @@ ANALYZE measurement; </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------4AB4E704D39F2BF05E79CD67 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------4AB4E704D39F2BF05E79CD67-- ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning @ 2017-03-03 07:39 amit <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: amit @ 2017-03-03 07:39 UTC (permalink / raw) Merge sections Partitioned Tables and Partitioning into one section called Table Partitioning and Related Solutions. --- doc/src/sgml/ddl.sgml | 1359 +++++++++++++++++++++++++------------------------ 1 file changed, 707 insertions(+), 652 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 09b5b3ff70..a2dd39df54 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2772,14 +2772,181 @@ VALUES ('Albany', NULL, NULL, 'NY'); </sect2> </sect1> - <sect1 id="ddl-partitioned-tables"> - <title>Partitioned Tables</title> + <sect1 id="ddl-partitioning"> + <title>Table Partitioning and Related Solutions</title> + + <indexterm> + <primary>partitioning</primary> + </indexterm> + + <indexterm> + <primary>table</primary> + <secondary>partitioning</secondary> + </indexterm> <indexterm> <primary>partitioned table</primary> </indexterm> <para> + <productname>PostgreSQL</productname> supports basic table + partitioning. This section describes why and how to implement + partitioning as part of your database design. + </para> + + <sect2 id="ddl-partitioning-overview"> + <title>Overview</title> + + <para> + Partitioning refers to splitting what is logically one large table into + smaller physical pieces. Partitioning can provide several benefits: + <itemizedlist> + <listitem> + <para> + Query performance can be improved dramatically in certain situations, + particularly when most of the heavily accessed rows of the table are in a + single partition or a small number of partitions. The partitioning + substitutes for leading columns of indexes, reducing index size and + making it more likely that the heavily-used parts of the indexes + fit in memory. + </para> + </listitem> + + <listitem> + <para> + When queries or updates access a large percentage of a single + partition, performance can be improved by taking advantage + of sequential scan of that partition instead of using an + index and random access reads scattered across the whole table. + </para> + </listitem> + + <listitem> + <para> + Bulk loads and deletes can be accomplished by adding or removing + partitions, if that requirement is planned into the partitioning design. + Doing <command>ALTER TABLE DETACH PARTITION</> followed by + <command>DROP TABLE</> is far faster than a bulk operation. These + commands also entirely avoid the <command>VACUUM</command> overhead + caused by a bulk <command>DELETE</>. + </para> + </listitem> + + <listitem> + <para> + Seldom-used data can be migrated to cheaper and slower storage media. + </para> + </listitem> + </itemizedlist> + + The benefits will normally be worthwhile only when a table would + otherwise be very large. The exact point at which a table will + benefit from partitioning depends on the application, although a + rule of thumb is that the size of the table should exceed the physical + memory of the database server. + </para> + + <para> + The following forms of partitioning can be implemented in + <productname>PostgreSQL</productname>: + + <variablelist> + <varlistentry> + <term>Range Partitioning</term> + + <listitem> + <para> + The table is partitioned into <quote>ranges</quote> defined + by a key column or set of columns, with no overlap between + the ranges of values assigned to different partitions. For + example one might partition by date ranges, or by ranges of + identifiers for particular business objects. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>List Partitioning</term> + + <listitem> + <para> + The table is partitioned by explicitly listing which key values + appear in each partition. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following partitioning methods are currently supported: + + <variablelist> + <varlistentry> + <term>Declarative Partitioning</term> + + <listitem> + <para> + One creates a <firstterm>partitioned table</firstterm> by specifying + the partitioning method and a set of columns as the partition key. + <firstterm>Partitions</firstterm>, which contain actual data inserted + into the table, are created by specifying what subset of the data it + accepts. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using inheritance</term> + + <listitem> + <para> + Each partition must be created as a child table of a single parent + table. The parent table itself is normally empty; it exists just to + represent the entire data set. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Using UNION ALL views</term> + + <listitem> + <para> + One can define a <literal>UNION ALL</literal> view over + <literal>SELECT</literal> on individual tables, each of which + contains a partition of data. Partitions are added or removed + by updating the view definition. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Accessing Tables using BRIN Indexes</term> + + <listitem> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index is, + designed for handling very large tables in which certain columns + have some natural physical location within the table. Scanning + a large table using a <acronym>BRIN</acronym> index results in + reading only a portion of the table, which is often why partitioning + is implemented. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Each of the above mentioned methods is described below. + </para> + </sect2> + + <sect2 id="ddl-partitioning-declarative"> + <title>Declarative Partitioning</title> + + <para> PostgreSQL offers a way to specify how to divide a table into pieces called partitions. The table that is divided is referred to as a <firstterm>partitioned table</firstterm>. The specification consists @@ -2790,25 +2957,29 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> All rows inserted into a partitioned table will be routed to one of the <firstterm>partitions</firstterm> based on the value of the partition - key. Each partition has a subset defined by its <firstterm>partition - bounds</firstterm>. Currently supported partitioning methods include - range and list, wherein each partition is assigned a range of keys or - a list of keys, respectively. + key. Each partition has a subset of the data defined by its + <firstterm>partition bounds</firstterm>. Currently supported + partitioning methods include range and list, where each partition is + assigned a range of keys and a list of keys, respectively. </para> <para> Partitions may have their own indexes, constraints and default values, - distinct from other partitions. Partitions do not inherit indexes from - the partitioned table. + distinct from other partitions. Partitions do not currently inherit + indexes from the partitioned table. + </para> + + <para> + See <xref linkend="sql-createtable"> for more details creating partitioned + tables and partitions. </para> <para> Partitions may themselves be defined as partitioned tables, referred to as - <firstterm>sub-partitioning</firstterm>. See <xref linkend="sql-createtable"> - for more details creating partitioned tables and partitions. It is not - currently possible to alter a regular table into a partitioned table or - vice versa. However, it is possible to add a regular table containing - data into a partition of a partitioned table, or remove a partition; see + <firstterm>sub-partitioning</firstterm>. It is not currently possible to + alter a regular table into a partitioned table or vice versa. However, + it is possible to add a regular or partitioned table containing data into + a partition of a partitioned table, or remove a partition; see <xref linkend="sql-altertable"> to learn more about the <command>ATTACH PARTITION</> and <command>DETACH PARTITION</> sub-commands. </para> @@ -2823,8 +2994,8 @@ VALUES ('Albany', NULL, NULL, 'NY'); partitioned table and partitions do not participate in inheritance with regular tables. Since a partition hierarchy consisting of the partitioned table and its partitions is still an inheritance hierarchy, - all the normal rules of inheritance apply as described in the previous - section (<xref linkend="ddl-inherit">) with some exceptions, most notably: + all the normal rules of inheritance apply as described in + <xref linkend="ddl-inherit"> with some exceptions, most notably: <itemizedlist> <listitem> @@ -2840,13 +3011,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> The <literal>ONLY</literal> notation used to exclude child tables would either cause error or will be ignored in some cases for - partitioned tables. For example, specifying <literal>ONLY</literal> - when querying data from a partitioned table would not make much sense, - because all the data is contained in partitions, so this raises an - error. Specifying <literal>ONLY</literal> when modifying schema is - not desirable in certain cases with partitioned tables where it may be - fine for regular inheritance parents (for example, dropping a column - from only the parent); an error will be thrown in that case. + partitioned tables. Specifying <literal>ONLY</literal> when modifying + schema is not desirable in certain cases with partitioned tables + whereas it may be fine for regular inheritance parents (for example, + dropping a column from only the parent); an error will be thrown in + that case. </para> </listitem> @@ -2855,9 +3024,9 @@ VALUES ('Albany', NULL, NULL, 'NY'); Partitions cannot have columns that are not present in the parent. It is neither possible to specify columns when creating partitions with <command>CREATE TABLE</> nor is it possible to add columns to - partitions using <command>ALTER TABLE</>. Tables may be added with - <command>ALTER TABLE ... ATTACH PARTITION</> if their columns exactly - match the parent, including oids. + partitions using <command>ALTER TABLE</>. Tables may be added as a + partition with <command>ALTER TABLE ... ATTACH PARTITION</> only if + their columns exactly match the parent, including oids. </para> </listitem> @@ -2871,199 +3040,353 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Partitions can also be foreign tables (see <xref linkend="ddl-foreign-data">), + Partitions can also be foreign tables (see <xref linkend="sql-createforeigntable">), although certain limitations exist currently in their usage. For example, - data inserted into the partitioned table cannot be routed to foreign table - partitions. + data inserted into the partitioned table is currently not routed to foreign + table partitions. </para> + <sect3 id="ddl-partitioning-declarative-example"> + <title>Example</title> + <para> - There are currently the following limitations of using partitioned tables: - <itemizedlist> + Suppose we are constructing a database for a large ice cream company. + The company measures peak temperatures every day as well as ice cream + sales in each region. Conceptually, we want a table like: + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +); +</programlisting> + + We know that most queries will access just the last week's, month's or + quarter's data, since the main use of this table will be to prepare + online reports for management. To reduce the amount of old data that + needs to be stored, we decide to only keep the most recent 3 years + worth of data. At the beginning of each month we will remove the oldest + month's data. In this situation we can use partitioning to help us meet + all of our different requirements for the measurements table. + </para> + + <para> + To use declarative partitioning in this case, use the following steps: + + <orderedlist spacing="compact"> <listitem> <para> - It is currently not possible to add same set of indexes on all partitions - automatically. Indexes must be added to each partition with separate - commands. + Create <structname>measurement</structname> table as a partitioned + table by specifying the <literal>PARTITION BY</literal> clause, which + includes the partitioning method (<literal>RANGE</literal> or + <literal>LIST</literal>) and the list of column(s) to use as the + partition key. + +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting> </para> - </listitem> - <listitem> + <note> + <para> + To decide when to use multiple columns in the partition key for range + partitioning, consider whether queries accessing the partitioned + in question will include conditions that involve multiple columns, + especially the columns being considered to be the partition key. + If so, the optimizer can create a plan that will scan fewer partitions + if a query's conditions are such that there is equality constraint on + leading partition key columns, because they limit the number of + partitions of interest. The first partition key column with + inequality constraint also further eliminates some partitions of + those chosen by equality constraints on earlier columns. + </para> + </note> + <para> - It is currently not possible to define indexes on partitioned tables - that include all rows from all partitions in one global index. - Consequently, it is not possible to create constraints that are realized - using an index such as <literal>UNIQUE</>. + To be able to insert data into this table, one must create partitions, + as described below. </para> </listitem> <listitem> <para> - Since primary keys are not supported on partitioned tables, - foreign keys referencing partitioned tables are not supported, nor - are foreign key references from a partitioned table to some other table. + Create partitions. Each partition's definition must specify the bounds + that correspond to the partitioning method and partition key of the + parent. Note that specifying bounds such that the new partition's + values will overlap with those in one or more existing partitions will + cause an error. Inserting data into into the parent table that does + not map to one of the existing partitions will cause an error; + appropriate partition must be added manually. + </para> + + <para> + Partitions thus created are in every way normal <productname>PostgreSQL</> + tables (or, possibly, foreign tables). It is possible, for example, to + specify tablespace, storage parameters for each partition separately. + </para> + + <para> + It is not necessary to create table constraints describing partition + boundary condition for partitions. Instead, partition constraints are + generated implicitly from the partition bound specification whenever + there is need to refer to them. Also, since any data inserted into the + parent table is automatically inserted into the appropriate partition, + it is not necessary to create triggers for the same. + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + +CREATE TABLE measurement_y2006m03 PARTITION OF measurement + FOR VALUES FROM ('2006-03-01') TO ('2006-04-01') + +... +CREATE TABLE measurement_y2007m11 PARTITION OF measurement + FOR VALUES FROM ('2007-11-01') TO ('2007-12-01') + +CREATE TABLE measurement_y2007m12 PARTITION OF measurement + FOR VALUES FROM ('2007-12-01') TO ('2008-01-01') + TABLESPACE fasttablespace; + +CREATE TABLE measurement_y2008m01 PARTITION OF measurement + FOR VALUES FROM ('2008-01-01') TO ('2008-02-01') + TABLESPACE fasttablespace + WITH (parallel_workers = 4); +</programlisting> </para> + + <note> + <para> + To implement sub-partitioning, specify the + <literal>PARTITION BY</literal> clause in the commands used to create + individual partitions, for example: + +<programlisting> +CREATE TABLE measurement_y2006m02 PARTITION OF measurement + FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') + PARTITION BY RANGE (peaktemp); +</programlisting> + + After creating partitions of <structname>measurement_y2006m02</>, + any data inserted into <structname>measurement</> that is mapped to + <structname>measurement_y2006m02</> will be further redirected to one + of its partitions based on the <structfield>peaktemp</> column. + Partition key specified may overlap with the parent's partition key, + although care must be taken when specifying the bounds of + sub-partitions such that the accepted set of data constitutes a + subset of what a partition's own bounds allows; the system does not + try to check if that's really the case. + </para> + </note> </listitem> <listitem> <para> - Row triggers, if necessary, must be defined on individual partitions, not - the partitioned table as it is currently not supported. + Create an index on the key column(s), + as well as any other indexes you might want for every partition. + Note that it is currently not supported to propagate index definition + from the master partitioned table to its partitions; in fact, it is + not possible to define indexes on partitioned tables in the first + place. This might change in future releases. + +<programlisting> +CREATE INDEX ON measurement_y2006m02 (logdate); +CREATE INDEX ON measurement_y2006m03 (logdate); +... +CREATE INDEX ON measurement_y2007m11 (logdate); +CREATE INDEX ON measurement_y2007m12 (logdate); +CREATE INDEX ON measurement_y2008m01 (logdate); +</programlisting> </para> </listitem> - </itemizedlist> + + <listitem> + <para> + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. + </para> + </listitem> + </orderedlist> </para> <para> - A detailed example that shows how to use partitioned tables is discussed in - the next chapter. + In the above example we would be creating a new partition each month, so + it might be wise to write a script that generates the required DDL + automatically. </para> - - </sect1> + </sect3> - <sect1 id="ddl-partitioning"> - <title>Partitioning</title> + <sect3 id="ddl-partitioning-declarative-maintenance"> + <title>Partition Maintenance</title> - <indexterm> - <primary>partitioning</primary> - </indexterm> + <para> + Normally the set of partitions established when initially defining the + the table are not intended to remain static. It is common to want to + remove old partitions of data and periodically add new partitions for + new data. One of the most important advantages of partitioning is + precisely that it allows this otherwise painful task to be executed + nearly instantaneously by manipulating the partition structure, rather + than physically moving large amounts of data around. + </para> - <indexterm> - <primary>table</primary> - <secondary>partitioning</secondary> - </indexterm> + <para> + The simplest option for removing old data is simply to drop the partition + that is no longer necessary: +<programlisting> +DROP TABLE measurement_y2006m02; +</programlisting> + This can very quickly delete millions of records because it doesn't have + to individually delete every record. Note however that the above command + requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the parent + table. + </para> <para> - <productname>PostgreSQL</productname> supports basic table - partitioning. This section describes why and how to implement - partitioning as part of your database design. - </para> + Another option that is often preferable is to remove the partition from + the partitioned table but retain access to it as a table in its own + right: - <sect2 id="ddl-partitioning-overview"> - <title>Overview</title> +<programlisting> +ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +</programlisting> + + This allows further operations to be performed on the data before + it is dropped. For example, this is often a useful time to back up + the data using <command>COPY</>, <application>pg_dump</>, or + similar tools. It might also be a useful time to aggregate data + into smaller formats, perform other data manipulations, or run + reports. + </para> <para> - Partitioning refers to splitting what is logically one large table - into smaller physical pieces. - Partitioning can provide several benefits: - <itemizedlist> - <listitem> - <para> - Query performance can be improved dramatically in certain situations, - particularly when most of the heavily accessed rows of the table are in a - single partition or a small number of partitions. The partitioning - substitutes for leading columns of indexes, reducing index size and - making it more likely that the heavily-used parts of the indexes - fit in memory. - </para> - </listitem> + Similarly we can add a new partition to handle new data. We can create an + empty partition in the partitioned table just as the original partitions + were created above: - <listitem> - <para> - When queries or updates access a large percentage of a single - partition, performance can be improved by taking advantage - of sequential scan of that partition instead of using an - index and random access reads scattered across the whole table. - </para> - </listitem> +<programlisting> +CREATE TABLE measurement_y2008m02 PARTITION OF measurement + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01') + TABLESPACE fasttablespace; +</programlisting> - <listitem> - <para> - Bulk loads and deletes can be accomplished by adding or removing - partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> or <command>ALTER TABLE DETACH PARTITION</> - and <command>DROP TABLE</> are both far faster than a bulk operation. - These commands also entirely avoid the <command>VACUUM</command> - overhead caused by a bulk <command>DELETE</>. - </para> - </listitem> + As an alternative, it is sometimes more convenient to create the + new table outside the partition structure, and make it a proper + partition later. This allows the data to be loaded, checked, and + transformed prior to it appearing in the partitioned table: - <listitem> - <para> - Seldom-used data can be migrated to cheaper and slower storage media. - </para> - </listitem> - </itemizedlist> +<programlisting> +CREATE TABLE measurement_y2008m02 + (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS) + TABLESPACE fasttablespace; - The benefits will normally be worthwhile only when a table would - otherwise be very large. The exact point at which a table will - benefit from partitioning depends on the application, although a - rule of thumb is that the size of the table should exceed the physical - memory of the database server. - </para> +ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 + CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); - <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - using two methods: +\copy measurement_y2008m02 from 'measurement_y2008m02' +-- possibly some other data preparation work - <variablelist> - <varlistentry> - <term>Using Table Inheritance</term> +ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 + FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +</programlisting> + </para> - <listitem> - <para> - Each partition must be created as a child table of a single parent - table. The parent table itself is normally empty; it exists just to - represent the entire data set. You should be familiar with - inheritance (see <xref linkend="ddl-inherit">) before attempting to - set up partitioning with it. This was the only method to implement - partitioning in older versions. - </para> - </listitem> - </varlistentry> + <tip> + <para> + Before running the <command>ATTACH PARTITION</> command, it is + recommended to create a <literal>CHECK</> constraint on the table to + be attached describing the desired partition constraint. Using the + same, system is able to skip the scan to validate the implicit + partition constraint. Without such a constraint, the table will be + scanned to validate the partition constraint, while holding an + <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. + One may want to drop the constraint after <command>ATTACH PARTITION</> + is finished, because it is no longer necessary. + </para> + </tip> + </sect3> - <varlistentry> - <term>Using Partitioned Tables</term> + <sect3 id="ddl-partitioning-declarative-limitations"> + <title>Limitations</title> - <listitem> - <para> - See last section for some general information: - <xref linkend="ddl-partitioned-tables"> - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <para> + There are currently the following limitations of using partitioned tables: + <itemizedlist> + <listitem> + <para> + It is currently not possible to add same set of indexes on all partitions + automatically. Indexes must be added to each partition with separate + commands. + </para> + </listitem> - <para> - The following forms of partitioning can be implemented in - <productname>PostgreSQL</productname> using either of the above mentioned - methods, although the latter provides dedicated syntax for each: + <listitem> + <para> + It is currently not possible to define indexes on partitioned tables + that include all rows from all partitions in one global index. + Consequently, it is not possible to create constraints that are realized + using an index such as <literal>UNIQUE</>. + </para> + </listitem> - <variablelist> - <varlistentry> - <term>Range Partitioning</term> + <listitem> + <para> + Since primary keys are not supported on partitioned tables, + foreign keys referencing partitioned tables are not supported, nor + are foreign key references from a partitioned table to some other table. + </para> + </listitem> - <listitem> - <para> - The table is partitioned into <quote>ranges</quote> defined - by a key column or set of columns, with no overlap between - the ranges of values assigned to different partitions. For - example one might partition by date ranges, or by ranges of - identifiers for particular business objects. - </para> - </listitem> - </varlistentry> + <listitem> + <para> + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clause are currently not allowed on partitioned tables. + </para> + </listitem> - <varlistentry> - <term>List Partitioning</term> + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. This might + change in future releases. + </para> + </listitem> - <listitem> - <para> - The table is partitioned by explicitly listing which key values - appear in each partition. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> + <listitem> + <para> + Row triggers, if necessary, must be defined on individual partitions, not + the partitioned table as it is currently not supported. + </para> + </listitem> + </itemizedlist> + </para> + </sect3> </sect2> - <sect2 id="ddl-partitioning-implementation"> - <title>Implementing Partitioning</title> + <sect2 id="ddl-partitioning-implementation-inheritance"> + <title>Implementation Using Inheritance</title> + <para> + In some cases, one may want to add columns to partitions that are not + present in the parent table which is not possible to do with the above + method. For such cases, partitioning can be implemented using + inheritance (see <xref linkend="ddl-inherit">). + </para> + + <sect3 id="ddl-partitioning-inheritance-example"> + <title>Example</title> <para> - To set up a partitioned table using inheritance, do the following: + We use the same <structname>measurement</structname> table we used + above. To implement it as a partitioned table using inheritance, do the + following: <orderedlist spacing="compact"> <listitem> <para> @@ -3076,6 +3399,11 @@ VALUES ('Albany', NULL, NULL, 'NY'); be applied equally to all partitions. There is no point in defining any indexes or unique constraints on it, either. </para> + + <para> + In case of our example, master table is the original + <structname>measurement</structname> as originally defined. + </para> </listitem> <listitem> @@ -3090,12 +3418,27 @@ VALUES ('Albany', NULL, NULL, 'NY'); are in every way normal <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> + + <para> + This solves one of our problems: deleting old data. Each + month, all we will need to do is perform a <command>DROP + TABLE</command> on the oldest child table and create a new + child table for the new month's data. +<programlisting> +CREATE TABLE measurement_y2006m02 () INHERITS (measurement); +CREATE TABLE measurement_y2006m03 () INHERITS (measurement); +... +CREATE TABLE measurement_y2007m11 () INHERITS (measurement); +CREATE TABLE measurement_y2007m12 () INHERITS (measurement); +CREATE TABLE measurement_y2008m01 () INHERITS (measurement); +</programlisting> + </para> </listitem> <listitem> <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. + Add non-overlapping table constraints to the partition tables to + define the allowed key values in each partition. </para> <para> @@ -3117,230 +3460,53 @@ CHECK ( outletID BETWEEN 200 AND 300 ) </para> <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. - </para> - </listitem> - - <listitem> - <para> - For each partition, create an index on the key column(s), - as well as any other indexes you might want. (The key index is - not strictly necessary, but in most scenarios it is helpful. - If you intend the key values to be unique then you should - always create a unique or primary-key constraint for each - partition.) - </para> - </listitem> - - <listitem> - <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. - </para> - </listitem> - - <listitem> - <para> - Ensure that the <xref linkend="guc-constraint-exclusion"> - configuration parameter is not disabled in - <filename>postgresql.conf</>. - If it is, queries will not be optimized as desired. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - To use partitioned tables, do the following: - <orderedlist spacing="compact"> - <listitem> - <para> - Create <quote>master</quote> table as a partitioned table by - specifying the <literal>PARTITION BY</literal> clause, which includes - the partitioning method (<literal>RANGE</literal> or - <literal>LIST</literal>) and the list of column(s) to use as the - partition key. To be able to insert data into the table, one must - create partitions, as described below. - </para> - - <note> - <para> - To decide when to use multiple columns in the partition key for range - partitioning, consider whether queries accessing the partitioned - in question will include conditions that involve multiple columns, - especially the columns being considered to be the partition key. - If so, the optimizer can create a plan that will scan fewer partitions - if a query's conditions are such that there is equality constraint on - leading partition key columns, because they limit the number of - partitions of interest. The first partition key column with - inequality constraint also further eliminates some partitions of - those chosen by equality constraints on earlier columns. - </para> - </note> - </listitem> - - <listitem> - <para> - Create partitions of the master partitioned table, with the partition - bounds specified for each partition matching the partitioning method - and partition key of the master table. Note that specifying partition - bounds such that the new partition's values will overlap with one or - more existing partitions will cause an error. It is only after - creating partitions that one is able to insert data into the master - partitioned table, provided it maps to one of the existing partitions. - If a data row does not map to any of the existing partitions, it will - cause an error. - </para> - - <para> - Partitions thus created are also in every way normal - <productname>PostgreSQL</> tables (or, possibly, foreign tables), - whereas partitioned tables differ in a number of ways. - </para> - - <para> - It is not necessary to create table constraints for partitions. - Instead, partition constraints are generated implicitly whenever - there is a need to refer to them. Also, since any data inserted into - the master partitioned table is automatically inserted into the - appropriate partition, it is not necessary to create triggers for the - same. - </para> - </listitem> - - <listitem> - <para> - Just like with inheritance, create an index on the key column(s), - as well as any other indexes you might want for every partition. - Note that it is currently not supported to propagate index definition - from the master partitioned table to its partitions; in fact, it is - not possible to define indexes on partitioned tables in the first - place. This might change in future releases. - </para> - </listitem> - - <listitem> - <para> - Currently, partitioned tables also depend on constraint exclusion - for query optimization, so ensure that the - <xref linkend="guc-constraint-exclusion"> configuration parameter is - not disabled in <filename>postgresql.conf</>. This might change in - future releases. - </para> - </listitem> - - </orderedlist> - </para> - - <para> - For example, suppose we are constructing a database for a large - ice cream company. The company measures peak temperatures every - day as well as ice cream sales in each region. Conceptually, - we want a table like: - -<programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -); -</programlisting> - - We know that most queries will access just the last week's, month's or - quarter's data, since the main use of this table will be to prepare - online reports for management. - To reduce the amount of old data that needs to be stored, we - decide to only keep the most recent 3 years worth of data. At the - beginning of each month we will remove the oldest month's data. - </para> - - <para> - In this situation we can use partitioning to help us meet all of our - different requirements for the measurements table. Following the - steps outlined above for both methods, partitioning can be set up as - follows: - </para> - - <para> - <orderedlist spacing="compact"> - <listitem> - <para> - The master table is the <structname>measurement</> table, declared - exactly as above. - </para> - </listitem> - - <listitem> - <para> - Next we create one partition for each active month: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); -</programlisting> - - Each of the partitions are complete tables in their own right, - but they inherit their definitions from the - <structname>measurement</> table. - </para> - - <para> - This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: + It would be better to instead create partitions as follows: <programlisting> CREATE TABLE measurement_y2006m02 ( CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2006m03 ( CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' ) ) INHERITS (measurement); + ... CREATE TABLE measurement_y2007m11 ( CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2007m12 ( CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' ) ) INHERITS (measurement); + CREATE TABLE measurement_y2008m01 ( CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) ) INHERITS (measurement); </programlisting> </para> + + <para> + Note that there is no difference in syntax between range and list + partitioning; those terms are descriptive only. + </para> </listitem> <listitem> <para> - We probably need indexes on the key columns too: - + For each partition, create an index on the key column(s), + as well as any other indexes you might want. (The key index is + not strictly necessary, but in most scenarios it is helpful. + If you intend the key values to be unique then you should + always create a unique or primary-key constraint for each + partition.) <programlisting> CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); -... CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); </programlisting> - - We choose not to add further indexes at this time. </para> </listitem> @@ -3363,7 +3529,9 @@ END; $$ LANGUAGE plpgsql; </programlisting> + </para> + <para> After creating the function, we create a trigger which calls the trigger function: @@ -3425,151 +3593,88 @@ LANGUAGE plpgsql; of this example. </para> </note> - </listitem> - </orderedlist> - </para> - - <para> - Steps when using a partitioned table are as follows: - </para> - <para> - <orderedlist spacing="compact"> - <listitem> <para> - Create the <structname>measurement</> table as a partitioned table: + A different approach to redirecting inserts into the appropriate + partition table is to set up rules, instead of a trigger, on the + master table. For example: <programlisting> -CREATE TABLE measurement ( - city_id int not null, - logdate date not null, - peaktemp int, - unitsales int -) PARTITION BY RANGE (logdate); +CREATE RULE measurement_insert_y2006m02 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) +DO INSTEAD + INSERT INTO measurement_y2006m02 VALUES (NEW.*); +... +CREATE RULE measurement_insert_y2008m01 AS +ON INSERT TO measurement WHERE + ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) +DO INSTEAD + INSERT INTO measurement_y2008m01 VALUES (NEW.*); </programlisting> + + A rule has significantly more overhead than a trigger, but the overhead + is paid once per query rather than once per row, so this method might + be advantageous for bulk-insert situations. In most cases, however, + the trigger method will offer better performance. </para> - </listitem> - <listitem> <para> - Then create partitions as follows: + Be aware that <command>COPY</> ignores rules. If you want to + use <command>COPY</> to insert data, you'll need to copy into the + correct partition table rather than into the master. <command>COPY</> + does fire triggers, so you can use it normally if you use the trigger + approach. + </para> -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01'); -CREATE TABLE measurement_y2006m03 PARTITION OF measurement - FOR VALUES FROM ('2006-03-01') TO ('2006-04-01'); -... -CREATE TABLE measurement_y2007m11 PARTITION OF measurement - FOR VALUES FROM ('2007-11-01') TO ('2007-12-01'); -CREATE TABLE measurement_y2007m12 PARTITION OF measurement - FOR VALUES FROM ('2007-12-01') TO ('2008-01-01'); -CREATE TABLE measurement_y2008m01 PARTITION OF measurement - FOR VALUES FROM ('2008-01-01') TO ('2008-02-01'); -</programlisting> + <para> + Another disadvantage of the rule approach is that there is no simple + way to force an error if the set of rules doesn't cover the insertion + date; the data will silently go into the master table instead. </para> </listitem> <listitem> <para> - Create indexes on the key columns just like in case of inheritance - partitions. + Ensure that the <xref linkend="guc-constraint-exclusion"> + configuration parameter is not disabled in + <filename>postgresql.conf</>. + If it is, queries will not be optimized as desired. </para> </listitem> </orderedlist> - - <note> - <para> - To implement sub-partitioning, specify the - <literal>PARTITION BY</literal> clause in the commands used to create - individual partitions, for example: - -<programlisting> -CREATE TABLE measurement_y2006m02 PARTITION OF measurement - FOR VALUES FROM ('2006-02-01') TO ('2006-03-01') - PARTITION BY RANGE (peaktemp); -</programlisting> - - After creating partitions of <structname>measurement_y2006m02</>, any - data inserted into <structname>measurement</> that is mapped to - <structname>measurement_y2006m02</> will be further redirected to one - of its partitions based on the <structfield>peaktemp</> column. - Partition key specified may overlap with the parent's partition key, - although care must be taken when specifying the bounds of sub-partitions - such that the accepted set of data constitutes a subset of what a - partition's own bounds allows; the system does not try to check if - that's really the case. - </para> - </note> </para> <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL, although significantly less when using - partitioned tables. In the above example we would be creating a new - partition each month, so it might be wise to write a script that - generates the required DDL automatically. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-managing-partitions"> - <title>Managing Partitions</title> - - <para> - Normally the set of partitions established when initially - defining the table are not intended to remain static. It is - common to want to remove old partitions of data and periodically - add new partitions for new data. One of the most important - advantages of partitioning is precisely that it allows this - otherwise painful task to be executed nearly instantaneously by - manipulating the partition structure, rather than physically moving large - amounts of data around. - </para> - - <para> - Both the inheritance-based and partitioned table methods allow this to - be done, although the latter requires taking an <literal>ACCESS EXCLUSIVE</literal> - lock on the master table for various commands mentioned below. - </para> + As we can see, a complex partitioning scheme could require a + substantial amount of DDL. In the above example we would be creating + a new partition each month, so it might be wise to write a script that + generates the required DDL automatically. + </para> + </sect3> - <para> - The simplest option for removing old data is simply to drop the partition - that is no longer necessary, which works using both methods of - partitioning: + <sect3 id="ddl-partitioning-inheritance-maintenance"> + <title>Partition Maintenance</title> + <para> + To remove old data quickly, simply to drop the partition that is no + longer necessary: <programlisting> DROP TABLE measurement_y2006m02; </programlisting> - This can very quickly delete millions of records because it doesn't have - to individually delete every record. - </para> + </para> <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> - - When using a partitioned table: + To remove the partition from the partitioned table but retain access to + it as a table in its own right: <programlisting> -ALTER TABLE measurement DETACH PARTITION measurement_y2006m02; +ALTER TABLE measurement_y2006m02 NO INHERIT measurement; </programlisting> - - This allows further operations to be performed on the data before - it is dropped. For example, this is often a useful time to back up - the data using <command>COPY</>, <application>pg_dump</>, or - similar tools. It might also be a useful time to aggregate data - into smaller formats, perform other data manipulations, or run - reports. </para> <para> - Similarly we can add a new partition to handle new data. We can create an - empty partition in the partitioned table just as the original partitions - were created above: + To add a new partition to handle new data, create an empty partition just + as the original partitions were created above: <programlisting> CREATE TABLE measurement_y2008m02 ( @@ -3577,52 +3682,80 @@ CREATE TABLE measurement_y2008m02 ( ) INHERITS (measurement); </programlisting> - When using a partitioned table: - -<programlisting> -CREATE TABLE measurement_y2008m02 PARTITION OF measurement - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01'); -</programlisting> - - As an alternative, it is sometimes more convenient to create the - new table outside the partition structure, and make it a proper - partition later. This allows the data to be loaded, checked, and - transformed prior to it appearing in the partitioned table: + Alternatively, one may created the new table outside the partition + structure, and make it a partition after data is loaded, checked, + and transformed. <programlisting> CREATE TABLE measurement_y2008m02 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); + ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' ); + \copy measurement_y2008m02 from 'measurement_y2008m02' -- possibly some other data preparation work + ALTER TABLE measurement_y2008m02 INHERIT measurement; </programlisting> + </para> + </sect3> + + <sect3 id="ddl-partitioning-inheritance-caveats"> + <title>Caveats</title> + + <para> + The following caveats apply to partitioned tables implemented using + inheritance: + <itemizedlist> + <listitem> + <para> + There is no automatic way to verify that all of the + <literal>CHECK</literal> constraints are mutually + exclusive. It is safer to create code that generates + partitions and creates and/or modifies associated objects than + to write each by hand. + </para> + </listitem> - The last of the above commands when using a partitioned table would be: + <listitem> + <para> + The schemes shown here assume that the partition key column(s) + of a row never change, or at least do not change enough to require + it to move to another partition. An <command>UPDATE</> that attempts + to do that will fail because of the <literal>CHECK</> constraints. + If you need to handle such cases, you can put suitable update triggers + on the partition tables, but it makes management of the structure + much more complicated. + </para> + </listitem> + <listitem> + <para> + If you are using manual <command>VACUUM</command> or + <command>ANALYZE</command> commands, don't forget that + you need to run them on each partition individually. A command like: <programlisting> -ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 - FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' ); +ANALYZE measurement; </programlisting> - </para> + will only process the master table. + </para> + </listitem> - <tip> + <listitem> <para> - Before running the <command>ATTACH PARTITION</> command, it is - recommended to create a <literal>CHECK</> constraint on the table to - be attached describing the desired partition constraint. Using the - same, system is able to skip the scan to validate the implicit - partition constraint. Without such a constraint, the table will be - scanned to validate the partition constraint, while holding an - <literal>ACCESS EXCLUSIVE</literal> lock on the parent table. - One may want to drop the constraint after <command>ATTACH PARTITION</> - is finished, because it is no longer necessary. + <command>INSERT</command> statements with <literal>ON CONFLICT</> + clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> + action is only taken in case of unique violations on the specified + target relation, not its child relations. </para> - </tip> - </sect2> + </listitem> + </itemizedlist> + </para> + </sect3> + </sect2> - <sect2 id="ddl-partitioning-constraint-exclusion"> + <sect2 id="ddl-partitioning-constraint-exclusion"> <title>Partitioning and Constraint Exclusion</title> <indexterm> @@ -3632,7 +3765,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 <para> <firstterm>Constraint exclusion</> is a query optimization technique that improves performance for partitioned tables defined in the - fashion described above. As an example: + fashion described above (both declarative partitioned tables and those + implemented using inheritance). As an example: <programlisting> SET constraint_exclusion = on; @@ -3715,153 +3849,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; are unlikely to benefit. </para> - <note> - <para> - Currently, constraint exclusion is also used for partitioned tables. - However, we did not create any <literal>CHECK</literal> constraints - for individual partitions as seen above. In this case, the optimizer - uses internally generated constraint for every partition. - </para> - </note> - - </sect2> - - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table (unless it is a partitioned table). For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - - <sect2 id="ddl-partitioning-caveats"> - <title>Caveats</title> - - <para> - The following caveats apply to using inheritance to implement partitioning: - <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> - - <listitem> - <para> - The schemes shown here assume that the partition key column(s) - of a row never change, or at least do not change enough to require - it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. - </para> - </listitem> - - <listitem> - <para> - If you are using manual <command>VACUUM</command> or - <command>ANALYZE</command> commands, don't forget that - you need to run them on each partition individually. A command like: -<programlisting> -ANALYZE measurement; -</programlisting> - will only process the master table. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. - </para> - </listitem> - </itemizedlist> - </para> - - <para> - The following caveats apply to partitioned tables created with the - explicit syntax: - <itemizedlist> - <listitem> - <para> - An <command>UPDATE</> that causes a row to move from one partition to - another fails, because the new value of the row fails to satisfy the - implicit partition constraint of the original partition. This might - change in future releases. - </para> - </listitem> - - <listitem> - <para> - <command>INSERT</command> statements with <literal>ON CONFLICT</> - clause are currently not allowed on partitioned tables. - </para> - </listitem> - - </itemizedlist> - </para> - <para> The following caveats apply to constraint exclusion, which is currently used by both inheritance and partitioned tables: @@ -3901,10 +3888,78 @@ ANALYZE measurement; don't try to use many thousands of partitions. </para> </listitem> - </itemizedlist> </para> </sect2> + + <sect2 id="ddl-partitioning-alternatives"> + <title>Alternative Partitioning Methods</title> + + <sect3 id="ddl-partitioning-alternatives-union-all"> + <title>Using UNION ALL view</title> + <para> + Partitioning can also be arranged using a <literal>UNION ALL</literal> + view, instead of table inheritance. For example, + +<programlisting> +CREATE VIEW measurement AS + SELECT * FROM measurement_y2006m02 +UNION ALL SELECT * FROM measurement_y2006m03 +... +UNION ALL SELECT * FROM measurement_y2007m11 +UNION ALL SELECT * FROM measurement_y2007m12 +UNION ALL SELECT * FROM measurement_y2008m01; +</programlisting> + + However, the need to recreate the view adds an extra step to adding and + dropping individual partitions of the data set. In practice this + method has little to recommend it compared to using inheritance. + </para> + </sect3> + + <sect3 id="ddl-partitioning-alternatives-brin-index"> + <title>Accessing Tables Using BRIN Index</title> + <para> + <acronym>BRIN</acronym>, which stands for Block Range Index, is + designed for handling very large tables in which certain columns + have some natural physical location within the table. For example, + in the <structname>measurement</structname> table, the entries for + earlier times (<structfield>logdate</structfield> column) will appear + earlier in the table most of the time. A table storing a ZIP code + column might have all codes for a city grouped together naturally. + </para> + + <para> + In case of <structname>measurement</structname> table, one may consider + adding a minmax <acronym>BRIN</acronym> index on the + <structfield>logdate</structfield> column. + +<programlisting> +CREATE INDEX ON measurement USING brin (logdate date_minmax_ops); +</programlisting> + + In this case, specifying <literal>date_minmax_ops</literal> is not + necessary; it is shown for clarity. + </para> + + <para> + <acronym>BRIN</acronym> indexes leverage this locality of data and + store summary information for a range of consecutive pages and keep + it updated as the data is added or removed. Because a + <acronym>BRIN</acronym> index is very small, scanning the index adds + adds little overhead compared to a sequential scan, but may avoid + scanning large parts of the table that are known not to contain + matching tuples. That is often why table partitioning is used. Thus, + <acronym>BRIN</acronym> indexes provide a subset of benefits that + parttioning provides with much less upfront setup. + </para> + + <para> + See <xref linkend="brin"> for more details. + </para> + </sect3> + + </sect2> </sect1> <sect1 id="ddl-foreign-data"> -- 2.11.0 --------------434C61868FD2E7C005F82712 Content-Type: text/x-diff; name="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Add-a-note-about-DROP-NOT-NULL-and-partitions.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
* [PATCH v46 2/7] Add CONCURRENTLY option to REPACK command. @ 2026-03-11 14:16 Antonin Houska <[email protected]> 0 siblings, 0 replies; 50+ messages in thread From: Antonin Houska @ 2026-03-11 14:16 UTC (permalink / raw) The REPACK command copies the relation data into a new file, creates new indexes and eventually swaps the files. To make sure that the old file does not change during the copying, the relation is locked in an exclusive mode, which prevents applications from both reading and writing. (To keep the data consistent, we'd only need to prevent the applications from writing, but even reading needs to be blocked before we can swap the files - otherwise some applications could continue using the old file. Currently, REPACK takes the simple approach and acquires the exclusive lock in the beginning. This patch introduces an alternative workflow, which only requires the exclusive lock when the relation (and index) files are being swapped. (Supposedly, the swapping should be pretty fast.) On the other hand, when we copy the data to the new file, we allow applications to read from the relation and even to write to it. First, we scan the relation using a "historic snapshot", and insert all the tuples satisfying this snapshot into the new relation. Second, logical decoding is used to capture the data changes done by applications during the copying (i.e. changes not yet committed from the perspective of the historic snapshot mentioned above), and those are applied to the new file before we acquire the exclusive lock that we need to swap the files. (Of course, more data changes can take place while we are waiting for the lock - these will be applied to the new file after we have acquired the lock and before we swap the files.) While the "concurrent data" changes are applied at specific stages (we cannot do that until the intial copy is finished and indexes are built), a background worker performs the decoding all the time. This way we minimize the amount of not-yet-decoded WAL, so that archiving / recycling of WAL segments is not delayed much. The decoded changes are written to files and passed to the backed performing REPACK. Since the logical decoding system, during its startup, waits until all the transactions which already have XID assigned have finished, there is a risk of deadlock if a transaction that already changed anything in the database tries to acquire a conflicting lock on the table REPACK CONCURRENTLY is working on. As an example, consider transaction running CREATE INDEX command on the table that is being REPACKed CONCURRENTLY. On the other hand, DML commands (INSERT, UPDATE, DELETE) are not a problem as their lock does not conflict with REPACK CONCURRENTLY. The current approach is that we accept the risk. If we tried to avoid it, it'd be necessary to unlock the table before the logical decoding is setup and lock it again afterwards. Such temporary unlocking would imply re-checking if the table still meets all the requirements for REPACK CONCURRENTLY. The WAL records produced by running DML commands on the new relation are intentionally not fed to the logical decoding system. Doing so would introduce significant overhead, and - as the new relation is never available for logical replication - it would be useless. --- doc/src/sgml/monitoring.sgml | 37 +- doc/src/sgml/mvcc.sgml | 12 +- doc/src/sgml/ref/repack.sgml | 112 +- src/Makefile | 1 + src/backend/access/heap/heapam.c | 41 +- src/backend/access/heap/heapam_handler.c | 221 +- src/backend/access/heap/rewriteheap.c | 6 +- src/backend/access/table/tableam.c | 3 +- src/backend/catalog/system_views.sql | 19 +- src/backend/commands/Makefile | 1 + src/backend/commands/cluster.c | 1823 ++++++++++++++++- src/backend/commands/matview.c | 1 + src/backend/commands/meson.build | 1 + src/backend/commands/repack_worker.c | 546 +++++ src/backend/commands/tablecmds.c | 1 + src/backend/commands/vacuum.c | 12 +- src/backend/executor/nodeModifyTable.c | 11 +- src/backend/libpq/pqmq.c | 5 + src/backend/meson.build | 1 + src/backend/postmaster/bgworker.c | 6 +- src/backend/replication/logical/decode.c | 37 +- src/backend/replication/logical/logical.c | 6 +- src/backend/replication/logical/snapbuild.c | 11 +- .../replication/pgoutput_repack/Makefile | 32 + .../replication/pgoutput_repack/meson.build | 18 + .../pgoutput_repack/pgoutput_repack.c | 290 +++ src/backend/replication/walsender.c | 2 +- src/backend/storage/ipc/procsignal.c | 4 + .../storage/lmgr/generate-lwlocknames.pl | 2 +- src/backend/tcop/postgres.c | 4 + .../utils/activity/wait_event_names.txt | 1 + src/backend/utils/time/snapmgr.c | 3 +- src/bin/psql/tab-complete.in.c | 4 +- src/include/access/heapam.h | 6 +- src/include/access/heapam_xlog.h | 2 + src/include/access/tableam.h | 39 +- src/include/commands/cluster.h | 16 +- src/include/commands/progress.h | 17 +- src/include/commands/repack_internal.h | 128 ++ src/include/replication/decode.h | 4 + src/include/replication/snapbuild.h | 2 +- src/include/storage/lockdefs.h | 4 +- src/include/storage/procsignal.h | 1 + src/include/utils/snapmgr.h | 2 + src/test/modules/injection_points/Makefile | 2 + .../injection_points/expected/repack.out | 113 + .../expected/repack_toast.out | 65 + src/test/modules/injection_points/meson.build | 2 + .../injection_points/specs/repack.spec | 142 ++ .../injection_points/specs/repack_toast.spec | 112 + src/test/regress/expected/rules.out | 19 +- src/tools/pgindent/typedefs.list | 6 + 52 files changed, 3664 insertions(+), 292 deletions(-) create mode 100644 src/backend/commands/repack_worker.c create mode 100644 src/backend/replication/pgoutput_repack/Makefile create mode 100644 src/backend/replication/pgoutput_repack/meson.build create mode 100644 src/backend/replication/pgoutput_repack/pgoutput_repack.c create mode 100644 src/include/commands/repack_internal.h create mode 100644 src/test/modules/injection_points/expected/repack.out create mode 100644 src/test/modules/injection_points/expected/repack_toast.out create mode 100644 src/test/modules/injection_points/specs/repack.spec create mode 100644 src/test/modules/injection_points/specs/repack_toast.spec diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..a0a155eb10b 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -6985,14 +6985,35 @@ FROM pg_stat_get_backend_idset() AS backendid; <row> <entry role="catalog_table_entry"><para role="column_definition"> - <structfield>heap_tuples_written</structfield> <type>bigint</type> + <structfield>heap_tuples_inserted</structfield> <type>bigint</type> </para> <para> - Number of heap tuples written. + Number of heap tuples inserted. This counter only advances when the phase is <literal>seq scanning heap</literal>, - <literal>index scanning heap</literal> - or <literal>writing new heap</literal>. + <literal>index scanning heap</literal>, + <literal>writing new heap</literal> + or <literal>catch-up</literal>. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>heap_tuples_updated</structfield> <type>bigint</type> + </para> + <para> + Number of heap tuples updated. + This counter only advances when the phase is <literal>catch-up</literal>. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>heap_tuples_deleted</structfield> <type>bigint</type> + </para> + <para> + Number of heap tuples deleted. + This counter only advances when the phase is <literal>catch-up</literal>. </para></entry> </row> @@ -7073,6 +7094,14 @@ FROM pg_stat_get_backend_idset() AS backendid; <command>REPACK</command> is currently writing the new heap. </entry> </row> + <row> + <entry><literal>catch-up</literal></entry> + <entry> + <command>REPACK CONCURRENTLY</command> is currently processing the DML + commands that other transactions executed during any of the preceding + phases. + </entry> + </row> <row> <entry><literal>swapping relation files</literal></entry> <entry> diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml index e775260936a..241caeb3593 100644 --- a/doc/src/sgml/mvcc.sgml +++ b/doc/src/sgml/mvcc.sgml @@ -1845,15 +1845,17 @@ SELECT pg_advisory_lock(q.id) FROM <title>Caveats</title> <para> - Some DDL commands, currently only <link linkend="sql-truncate"><command>TRUNCATE</command></link> and the - table-rewriting forms of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>, are not + Some commands, currently only <link linkend="sql-truncate"><command>TRUNCATE</command></link>, the + table-rewriting forms of <link linkend="sql-altertable"><command>ALTER + TABLE</command></link> and <command>REPACK</command> with + the <literal>CONCURRENTLY</literal> option, are not MVCC-safe. This means that after the truncation or rewrite commits, the table will appear empty to concurrent transactions, if they are using a - snapshot taken before the DDL command committed. This will only be an + snapshot taken before the command committed. This will only be an issue for a transaction that did not access the table in question - before the DDL command started — any transaction that has done so + before the command started — any transaction that has done so would hold at least an <literal>ACCESS SHARE</literal> table lock, - which would block the DDL command until that transaction completes. + which would block the truncating or rewriting command until that transaction completes. So these commands will not cause any apparent inconsistency in the table contents for successive queries on the target table, but they could cause visible inconsistency between the contents of the target diff --git a/doc/src/sgml/ref/repack.sgml b/doc/src/sgml/ref/repack.sgml index 8ccf7c7a417..170eb84bf43 100644 --- a/doc/src/sgml/ref/repack.sgml +++ b/doc/src/sgml/ref/repack.sgml @@ -28,6 +28,7 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING VERBOSE [ <replaceable class="parameter">boolean</replaceable> ] ANALYZE [ <replaceable class="parameter">boolean</replaceable> ] + CONCURRENTLY [ <replaceable class="parameter">boolean</replaceable> ] <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase> @@ -54,7 +55,8 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING processes every table and materialized view in the current database that the current user has the <literal>MAINTAIN</literal> privilege on. This form of <command>REPACK</command> cannot be executed inside a transaction - block. + block. Also, this form is not allowed if + the <literal>CONCURRENTLY</literal> option is used. </para> <para> @@ -67,7 +69,8 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING When a table is being repacked, an <literal>ACCESS EXCLUSIVE</literal> lock is acquired on it. This prevents any other database operations (both reads and writes) from operating on the table until the <command>REPACK</command> - is finished. + is finished. If you want to keep the table accessible during the repacking, + consider using the <literal>CONCURRENTLY</literal> option. </para> <refsect2 id="sql-repack-notes-on-clustering" xreflabel="Notes on Clustering"> @@ -198,6 +201,111 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING </listitem> </varlistentry> + <varlistentry> + <term><literal>CONCURRENTLY</literal></term> + <listitem> + <para> + Allow other transactions to use the table while it is being repacked. + </para> + + <para> + Internally, <command>REPACK</command> copies the contents of the table + (ignoring dead tuples) into a new file, sorted by the specified index, + and also creates a new file for each index. Then it swaps the old and + new files for the table and all the indexes, and deletes the old + files. The <literal>ACCESS EXCLUSIVE</literal> lock is needed to make + sure that the old files do not change during the processing because the + changes would get lost due to the swap. + </para> + + <para> + With the <literal>CONCURRENTLY</literal> option, the <literal>ACCESS + EXCLUSIVE</literal> lock is only acquired to swap the table and index + files. The data changes that took place during the creation of the new + table and index files are captured using logical decoding + (<xref linkend="logicaldecoding"/>) and applied before + the <literal>ACCESS EXCLUSIVE</literal> lock is requested. Thus the lock + is typically held only for the time needed to swap the files, which + should be pretty short. However, the time might still be noticeable if + too many data changes have been done to the table while + <command>REPACK</command> was waiting for the lock: those changes must + be processed just before the files are swapped, while the + <literal>ACCESS EXCLUSIVE</literal> lock is being held. + </para> + + <para> + Note that <command>REPACK</command> with the + <literal>CONCURRENTLY</literal> option does not try to order the rows + inserted into the table after the repacking started. Also + note <command>REPACK</command> might fail to complete due to DDL + commands executed on the table by other transactions during the + repacking. + </para> + + <note> + <para> + In addition to the temporary space requirements explained in + <xref linkend="sql-repack-notes-on-resources"/>, + the <literal>CONCURRENTLY</literal> option can add to the usage of + temporary space a bit more. The reason is that other transactions can + perform DML operations which cannot be applied to the new file until + <command>REPACK</command> has copied all the existing tuples from the + old file. Thus the tuples inserted into the old file during the copying + are also stored separately in a temporary file, until they can be + processed. + </para> + </note> + + <para> + The <literal>CONCURRENTLY</literal> option cannot be used in the + following cases: + + <itemizedlist> + <listitem> + <para> + The table is <literal>UNLOGGED</literal>. + </para> + </listitem> + + <listitem> + <para> + The table is partitioned. + </para> + </listitem> + + <listitem> + <para> + The table is a system catalog or a <acronym>TOAST</acronym> table. + </para> + </listitem> + + <listitem> + <para> + <command>REPACK</command> is executed inside a transaction block. + </para> + </listitem> + + <listitem> + <para> + The <link linkend="guc-max-replication-slots"><varname>max_replication_slots</varname></link> + configuration parameter does not allow for creation of an additional + replication slot. + </para> + </listitem> + </itemizedlist> + </para> + + <warning> + <para> + <command>REPACK</command> with the <literal>CONCURRENTLY</literal> + option is not MVCC-safe, see <xref linkend="mvcc-caveats"/> for + details. + </para> + </warning> + + </listitem> + </varlistentry> + <varlistentry> <term><literal>VERBOSE</literal></term> <listitem> diff --git a/src/Makefile b/src/Makefile index 2f31a2f20a7..b18c9a14ffa 100644 --- a/src/Makefile +++ b/src/Makefile @@ -23,6 +23,7 @@ SUBDIRS = \ interfaces \ backend/replication/libpqwalreceiver \ backend/replication/pgoutput \ + backend/replication/pgoutput_repack \ fe_utils \ bin \ pl \ diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index eb1f67f31cd..2ebf2770077 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -61,7 +61,8 @@ static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup, static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, Buffer newbuf, HeapTuple oldtup, HeapTuple newtup, HeapTuple old_key_tuple, - bool all_visible_cleared, bool new_all_visible_cleared); + bool all_visible_cleared, bool new_all_visible_cleared, + bool walLogical); #ifdef USE_ASSERT_CHECKING static void check_lock_if_inplace_updateable_rel(Relation relation, const ItemPointerData *otid, @@ -2850,8 +2851,8 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask) */ TM_Result heap_delete(Relation relation, const ItemPointerData *tid, - CommandId cid, Snapshot crosscheck, bool wait, - TM_FailureData *tmfd, bool changingPart) + CommandId cid, Snapshot crosscheck, int options, + TM_FailureData *tmfd) { TM_Result result; TransactionId xid = GetCurrentTransactionId(); @@ -2861,6 +2862,9 @@ heap_delete(Relation relation, const ItemPointerData *tid, BlockNumber block; Buffer buffer; Buffer vmbuffer = InvalidBuffer; + bool wait = (options & TABLE_DELETE_WAIT) != 0; + bool changingPart = (options & TABLE_DELETE_CHANGING_PART) != 0; + bool walLogical = (options & TABLE_DELETE_NO_LOGICAL) == 0; TransactionId new_xmax; uint16 new_infomask, new_infomask2; @@ -3098,7 +3102,8 @@ l1: * Compute replica identity tuple before entering the critical section so * we don't PANIC upon a memory allocation failure. */ - old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied); + old_key_tuple = walLogical ? + ExtractReplicaIdentity(relation, &tp, true, &old_key_copied) : NULL; /* * If this is the first possibly-multixact-able operation in the current @@ -3188,6 +3193,15 @@ l1: xlrec.flags |= XLH_DELETE_CONTAINS_OLD_KEY; } + /* + * Unlike UPDATE, DELETE is decoded even if there is no old key, so it + * does not help to clear both XLH_DELETE_CONTAINS_OLD_TUPLE and + * XLH_DELETE_CONTAINS_OLD_KEY. Thus we need an extra flag. TODO + * Consider not decoding tuples w/o the old tuple/key instead. + */ + if (!walLogical) + xlrec.flags |= XLH_DELETE_NO_LOGICAL; + XLogBeginInsert(); XLogRegisterData(&xlrec, SizeOfHeapDelete); @@ -3279,8 +3293,8 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid) result = heap_delete(relation, tid, GetCurrentCommandId(true), InvalidSnapshot, - true /* wait for commit */ , - &tmfd, false /* changingPart */ ); + TABLE_UPDATE_WAIT, + &tmfd); switch (result) { case TM_SelfModified: @@ -3319,7 +3333,7 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid) */ TM_Result heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, - CommandId cid, Snapshot crosscheck, bool wait, + CommandId cid, Snapshot crosscheck, int options, TM_FailureData *tmfd, LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes) { @@ -3336,6 +3350,8 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, HeapTuple heaptup; HeapTuple old_key_tuple = NULL; bool old_key_copied = false; + bool wait = (options & TABLE_UPDATE_WAIT) != 0; + bool walLogical = (options & TABLE_UPDATE_NO_LOGICAL) == 0; Page page, newpage; BlockNumber block; @@ -4217,7 +4233,8 @@ l2: newbuf, &oldtup, heaptup, old_key_tuple, all_visible_cleared, - all_visible_cleared_new); + all_visible_cleared_new, + walLogical); if (newbuf != buffer) { PageSetLSN(newpage, recptr); @@ -4574,7 +4591,7 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup result = heap_update(relation, otid, tup, GetCurrentCommandId(true), InvalidSnapshot, - true /* wait for commit */ , + TABLE_UPDATE_WAIT, &tmfd, &lockmode, update_indexes); switch (result) { @@ -8892,7 +8909,8 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, Buffer newbuf, HeapTuple oldtup, HeapTuple newtup, HeapTuple old_key_tuple, - bool all_visible_cleared, bool new_all_visible_cleared) + bool all_visible_cleared, bool new_all_visible_cleared, + bool walLogical) { xl_heap_update xlrec; xl_heap_header xlhdr; @@ -8903,7 +8921,8 @@ log_heap_update(Relation reln, Buffer oldbuf, suffixlen = 0; XLogRecPtr recptr; Page page = BufferGetPage(newbuf); - bool need_tuple_data = RelationIsLogicallyLogged(reln); + bool need_tuple_data = RelationIsLogicallyLogged(reln) && + walLogical; bool init; int bufflags; diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index d40878928e1..0ba7c0df1fc 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -311,22 +311,22 @@ heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot, static TM_Result heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid, - Snapshot snapshot, Snapshot crosscheck, bool wait, - TM_FailureData *tmfd, bool changingPart) + Snapshot snapshot, Snapshot crosscheck, int options, + TM_FailureData *tmfd) { /* * Currently Deleting of index tuples are handled at vacuum, in case if * the storage itself is cleaning the dead tuples by itself, it is the * time to call the index tuple deletion also. */ - return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart); + return heap_delete(relation, tid, cid, crosscheck, options, tmfd); } static TM_Result heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot, CommandId cid, Snapshot snapshot, Snapshot crosscheck, - bool wait, TM_FailureData *tmfd, + int options, TM_FailureData *tmfd, LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes) { bool shouldFree = true; @@ -337,7 +337,7 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot, slot->tts_tableOid = RelationGetRelid(relation); tuple->t_tableOid = slot->tts_tableOid; - result = heap_update(relation, otid, tuple, cid, crosscheck, wait, + result = heap_update(relation, otid, tuple, cid, crosscheck, options, tmfd, lockmode, update_indexes); ItemPointerCopy(&tuple->t_self, &slot->tts_tid); @@ -695,13 +695,14 @@ static void heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, Relation OldIndex, bool use_sort, TransactionId OldestXmin, + Snapshot snapshot, TransactionId *xid_cutoff, MultiXactId *multi_cutoff, double *num_tuples, double *tups_vacuumed, double *tups_recently_dead) { - RewriteState rwstate; + RewriteState rwstate = NULL; IndexScanDesc indexScan; TableScanDesc tableScan; HeapScanDesc heapScan; @@ -715,6 +716,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, bool *isnull; BufferHeapTupleTableSlot *hslot; BlockNumber prev_cblock = InvalidBlockNumber; + bool concurrent = snapshot != NULL; /* Remember if it's a system catalog */ is_system_catalog = IsSystemRelation(OldHeap); @@ -730,9 +732,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, values = palloc_array(Datum, natts); isnull = palloc_array(bool, natts); - /* Initialize the rewrite operation */ - rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff, - *multi_cutoff); + /* + * Initialize the rewrite operation. + */ + if (!concurrent) + rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, + *xid_cutoff, *multi_cutoff); /* Set up sorting if wanted */ @@ -747,6 +752,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, * Prepare to scan the OldHeap. To ensure we see recently-dead tuples * that still need to be copied, we scan with SnapshotAny and use * HeapTupleSatisfiesVacuum for the visibility test. + * + * In the CONCURRENTLY case, we do regular MVCC visibility tests, using + * the snapshot passed by the caller. */ if (OldIndex != NULL && !use_sort) { @@ -763,7 +771,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, tableScan = NULL; heapScan = NULL; - indexScan = index_beginscan(OldHeap, OldIndex, SnapshotAny, NULL, 0, 0); + indexScan = index_beginscan(OldHeap, OldIndex, + snapshot ? snapshot : SnapshotAny, + NULL, 0, 0); index_rescan(indexScan, NULL, 0, NULL, 0); } else @@ -772,7 +782,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, pgstat_progress_update_param(PROGRESS_REPACK_PHASE, PROGRESS_REPACK_PHASE_SEQ_SCAN_HEAP); - tableScan = table_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL); + tableScan = table_beginscan(OldHeap, + snapshot ? snapshot : SnapshotAny, + 0, (ScanKey) NULL); heapScan = (HeapScanDesc) tableScan; indexScan = NULL; @@ -848,83 +860,91 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, buf = hslot->buffer; /* - * To be able to guarantee that we can set the hint bit, acquire an - * exclusive lock on the old buffer. We need the hint bits, set in - * heapam_relation_copy_for_cluster() -> HeapTupleSatisfiesVacuum(), - * to be set, as otherwise reform_and_rewrite_tuple() -> - * rewrite_heap_tuple() will get confused. Specifically, - * rewrite_heap_tuple() checks for HEAP_XMAX_INVALID in the old tuple - * to determine whether to check the old-to-new mapping hash table. - * - * It'd be better if we somehow could avoid setting hint bits on the - * old page. One reason to use VACUUM FULL are very bloated tables - - * rewriting most of the old table during VACUUM FULL doesn't exactly - * help... + * Regarding CONCURRENTLY, see the comments on MVCC snapshot above. */ - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - - switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf)) + if (!concurrent) { - case HEAPTUPLE_DEAD: - /* Definitely dead */ - isdead = true; - break; - case HEAPTUPLE_RECENTLY_DEAD: - *tups_recently_dead += 1; - pg_fallthrough; - case HEAPTUPLE_LIVE: - /* Live or recently dead, must copy it */ - isdead = false; - break; - case HEAPTUPLE_INSERT_IN_PROGRESS: + /* + * To be able to guarantee that we can set the hint bit, acquire + * an exclusive lock on the old buffer. We need the hint bits, set + * in heapam_relation_copy_for_cluster() -> + * HeapTupleSatisfiesVacuum(), to be set, as otherwise + * reform_and_rewrite_tuple() -> rewrite_heap_tuple() will get + * confused. Specifically, rewrite_heap_tuple() checks for + * HEAP_XMAX_INVALID in the old tuple to determine whether to + * check the old-to-new mapping hash table. + * + * It'd be better if we somehow could avoid setting hint bits on + * the old page. One reason to use VACUUM FULL are very bloated + * tables - rewriting most of the old table during VACUUM FULL + * doesn't exactly help... + */ + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - /* - * Since we hold exclusive lock on the relation, normally the - * only way to see this is if it was inserted earlier in our - * own transaction. However, it can happen in system - * catalogs, since we tend to release write lock before commit - * there. Give a warning if neither case applies; but in any - * case we had better copy it. - */ - if (!is_system_catalog && - !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data))) - elog(WARNING, "concurrent insert in progress within table \"%s\"", - RelationGetRelationName(OldHeap)); - /* treat as live */ - isdead = false; - break; - case HEAPTUPLE_DELETE_IN_PROGRESS: - - /* - * Similar situation to INSERT_IN_PROGRESS case. - */ - if (!is_system_catalog && - !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data))) - elog(WARNING, "concurrent delete in progress within table \"%s\"", - RelationGetRelationName(OldHeap)); - /* treat as recently dead */ - *tups_recently_dead += 1; - isdead = false; - break; - default: - elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result"); - isdead = false; /* keep compiler quiet */ - break; - } - - LockBuffer(buf, BUFFER_LOCK_UNLOCK); - - if (isdead) - { - *tups_vacuumed += 1; - /* heap rewrite module still needs to see it... */ - if (rewrite_heap_dead_tuple(rwstate, tuple)) + switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf)) { - /* A previous recently-dead tuple is now known dead */ - *tups_vacuumed += 1; - *tups_recently_dead -= 1; + case HEAPTUPLE_DEAD: + /* Definitely dead */ + isdead = true; + break; + case HEAPTUPLE_RECENTLY_DEAD: + *tups_recently_dead += 1; + pg_fallthrough; + case HEAPTUPLE_LIVE: + /* Live or recently dead, must copy it */ + isdead = false; + break; + case HEAPTUPLE_INSERT_IN_PROGRESS: + + /* + * As long as we hold exclusive lock on the relation, + * normally the only way to see this is if it was inserted + * earlier in our own transaction. However, it can happen + * in system catalogs, since we tend to release write lock + * before commit there. Give a warning if neither case + * applies; but in any case we had better copy it. + */ + if (!is_system_catalog && + !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data))) + elog(WARNING, "concurrent insert in progress within table \"%s\"", + RelationGetRelationName(OldHeap)); + /* treat as live */ + isdead = false; + break; + case HEAPTUPLE_DELETE_IN_PROGRESS: + + /* + * Similar situation to INSERT_IN_PROGRESS case. + */ + if (!is_system_catalog && + !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data))) + elog(WARNING, "concurrent delete in progress within table \"%s\"", + RelationGetRelationName(OldHeap)); + /* treat as recently dead */ + *tups_recently_dead += 1; + isdead = false; + break; + default: + elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result"); + isdead = false; /* keep compiler quiet */ + break; + } + + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + + if (isdead) + { + *tups_vacuumed += 1; + /* heap rewrite module still needs to see it... */ + if (rewrite_heap_dead_tuple(rwstate, tuple)) + { + /* A previous recently-dead tuple is now known dead */ + *tups_vacuumed += 1; + *tups_recently_dead -= 1; + } + + continue; } - continue; } *num_tuples += 1; @@ -943,7 +963,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, { const int ct_index[] = { PROGRESS_REPACK_HEAP_TUPLES_SCANNED, - PROGRESS_REPACK_HEAP_TUPLES_WRITTEN + PROGRESS_REPACK_HEAP_TUPLES_INSERTED }; int64 ct_val[2]; @@ -1001,7 +1021,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, values, isnull, rwstate); /* Report n_tuples */ - pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_WRITTEN, + pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, n_tuples); } @@ -1009,7 +1029,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, } /* Write out any remaining tuples, and fsync if needed */ - end_heap_rewrite(rwstate); + if (rwstate) + end_heap_rewrite(rwstate); /* Clean up */ pfree(values); @@ -2402,6 +2423,10 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate, * SET WITHOUT OIDS. * * So, we must reconstruct the tuple from component Datums. + * + * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that + * case we still need to deform/form the tuple. TODO Shouldn't we rename the + * function, as might not do any rewrite? */ static void reform_and_rewrite_tuple(HeapTuple tuple, @@ -2424,8 +2449,28 @@ reform_and_rewrite_tuple(HeapTuple tuple, copiedTuple = heap_form_tuple(newTupDesc, values, isnull); - /* The heap rewrite module does the rest */ - rewrite_heap_tuple(rwstate, tuple, copiedTuple); + if (rwstate) + /* The heap rewrite module does the rest */ + rewrite_heap_tuple(rwstate, tuple, copiedTuple); + else + { + /* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * The following is like simple_heap_insert() except that we pass the + * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps + * the relation files, it drops this relation, so no logical + * replication subscription should need the data. + */ + heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true), + HEAP_INSERT_NO_LOGICAL, NULL); + } heap_freetuple(copiedTuple); } diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 6b19ac3030d..d706856e7a5 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -621,9 +621,9 @@ raw_heap_insert(RewriteState state, HeapTuple tup) int options = HEAP_INSERT_SKIP_FSM; /* - * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data - * for the TOAST table are not logically decoded. The main heap is - * WAL-logged as XLOG FPI records, which are not logically decoded. + * While rewriting the heap for REPACK, make sure data for the TOAST + * table are not logically decoded. The main heap is WAL-logged as + * XLOG FPI records, which are not logically decoded. */ options |= HEAP_INSERT_NO_LOGICAL; diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c index dfda1af412e..4c759f29b3e 100644 --- a/src/backend/access/table/tableam.c +++ b/src/backend/access/table/tableam.c @@ -319,8 +319,7 @@ simple_table_tuple_delete(Relation rel, ItemPointer tid, Snapshot snapshot) result = table_tuple_delete(rel, tid, GetCurrentCommandId(true), snapshot, InvalidSnapshot, - true /* wait for commit */ , - &tmfd, false /* changingPart */ ); + TABLE_DELETE_WAIT, &tmfd); switch (result) { diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..75f97cb23e3 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1343,16 +1343,19 @@ CREATE VIEW pg_stat_progress_repack AS WHEN 2 THEN 'index scanning heap' WHEN 3 THEN 'sorting tuples' WHEN 4 THEN 'writing new heap' - WHEN 5 THEN 'swapping relation files' - WHEN 6 THEN 'rebuilding index' - WHEN 7 THEN 'performing final cleanup' + WHEN 5 THEN 'catch-up' + WHEN 6 THEN 'swapping relation files' + WHEN 7 THEN 'rebuilding index' + WHEN 8 THEN 'performing final cleanup' END AS phase, CAST(S.param3 AS oid) AS repack_index_relid, S.param4 AS heap_tuples_scanned, - S.param5 AS heap_tuples_written, - S.param6 AS heap_blks_total, - S.param7 AS heap_blks_scanned, - S.param8 AS index_rebuild_count + S.param5 AS heap_tuples_inserted, + S.param6 AS heap_tuples_updated, + S.param7 AS heap_tuples_deleted, + S.param8 AS heap_blks_total, + S.param9 AS heap_blks_scanned, + S.param10 AS index_rebuild_count FROM pg_stat_get_progress_info('REPACK') AS S LEFT JOIN pg_database D ON S.datid = D.oid; @@ -1370,7 +1373,7 @@ CREATE VIEW pg_stat_progress_cluster AS phase, repack_index_relid AS cluster_index_relid, heap_tuples_scanned, - heap_tuples_written, + heap_tuples_inserted + heap_tuples_updated AS heap_tuples_written, heap_blks_total, heap_blks_scanned, index_rebuild_count diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index c10fdba2bbb..6926bc44818 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -51,6 +51,7 @@ OBJS = \ proclang.o \ propgraphcmds.o \ publicationcmds.o \ + repack_worker.o \ schemacmds.o \ seclabel.o \ sequence.o \ diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 09066db0956..33083a2b030 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -4,6 +4,22 @@ * REPACK a table; formerly known as CLUSTER. VACUUM FULL also uses * parts of this code. * + * There are two somewhat different ways to rewrite a table. In non- + * concurrent mode, it's easy: take AccessExclusiveLock, create a new + * transient relation, copy the tuples over to the relfilenode of the new + * relation, swap the relfilenodes, then drop the old relation. + * + * In concurrent mode, we lock the table with only ShareUpdateExclusiveLock, + * then do an initial copy as above. However, while the tuples are being + * copied, concurrent transactions could modify the table. To cope with those + * changes, we rely on logical decoding to obtain them from WAL. A bgworker + * consumes WAL while the initial copy is ongoing (to prevent excessive WAL + * from being reserved), and accumulates the changes in a file. Once the + * initial copy is complete, we read the changes from the file and re-apply + * them on the new heap. Then we upgrade our ShareUpdateExclusiveLock to + * AccessExclusiveLock and swap the relfilenodes. This way, the time we hold + * a strong lock on the table is much reduced, and the bloat is eliminated. + * * * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California @@ -17,6 +33,7 @@ #include "postgres.h" #include "access/amapi.h" +#include "access/detoast.h" #include "access/heapam.h" #include "access/multixact.h" #include "access/relscan.h" @@ -24,6 +41,7 @@ #include "access/toast_internals.h" #include "access/transam.h" #include "access/xact.h" +#include "access/xloginsert.h" #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/heap.h" @@ -31,22 +49,29 @@ #include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_am.h" +#include "catalog/pg_control.h" #include "catalog/pg_inherits.h" #include "catalog/toasting.h" #include "commands/cluster.h" #include "commands/defrem.h" #include "commands/progress.h" +#include "commands/repack_internal.h" #include "commands/tablecmds.h" #include "commands/vacuum.h" +#include "executor/executor.h" +#include "libpq/pqformat.h" +#include "libpq/pqmq.h" #include "miscadmin.h" #include "optimizer/optimizer.h" #include "pgstat.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" #include "storage/predicate.h" +#include "storage/proc.h" #include "utils/acl.h" #include "utils/fmgroids.h" #include "utils/guc.h" +#include "utils/injection_point.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -54,6 +79,7 @@ #include "utils/relmapper.h" #include "utils/snapmgr.h" #include "utils/syscache.h" +#include "utils/wait_event_types.h" /* * This struct is used to pass around the information on tables to be @@ -66,12 +92,79 @@ typedef struct Oid indexOid; } RelToCluster; +/* + * The first file exported by the decoding worker must contain a snapshot, the + * following ones contain the data changes. + */ +#define WORKER_FILE_SNAPSHOT 0 + +/* + * Information needed to apply concurrent data changes. + */ +typedef struct ChangeContext +{ + /* The relation the changes are applied to. */ + Relation cc_rel; + + /* Needed to update indexes of rel_dst. */ + ResultRelInfo *cc_rri; + EState *cc_estate; + + /* + * Existing tuples to UPDATE and DELETE are located via this index. We + * keep the scankey in partially initialized state to avoid repeated work. + * sk_argument is completed on the fly. + */ + Relation cc_ident_index; + ScanKey cc_ident_key; + int cc_ident_key_nentries; + + /* Sequential number of the file containing the changes. */ + int cc_file_seq; +} ChangeContext; + +/* FIXME probably in repack_worker.h */ +pid_t backend_pid; +ProcNumber backend_proc_number; + +/* + * Backend-local information to control the decoding worker. + */ +typedef struct DecodingWorker +{ + /* The worker. */ + BackgroundWorkerHandle *handle; + + /* DecodingWorkerShared is in this segment. */ + dsm_segment *seg; + + /* Handle of the error queue. */ + shm_mq_handle *error_mqh; +} DecodingWorker; + +/* Pointer to currently running decoding worker. */ +static DecodingWorker *decoding_worker = NULL; + +/* + * Is there a message sent by a repack worker that the backend needs to + * receive? + */ +volatile sig_atomic_t RepackMessagePending = false; + +static LOCKMODE RepackLockLevel(bool concurrent); static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, - Oid indexOid, Oid userid, int options); -static void rebuild_relation(Relation OldHeap, Relation index, bool verbose); + Oid indexOid, Oid userid, LOCKMODE lmode, + int options); +static void check_repack_concurrently_requirements(Relation rel, + Oid *ident_idx_p); +static void rebuild_relation(Relation OldHeap, Relation index, bool verbose, + Oid ident_idx); static void copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, - bool verbose, bool *pSwapToastByContent, - TransactionId *pFreezeXid, MultiXactId *pCutoffMulti); + Snapshot snapshot, + bool verbose, + bool *pSwapToastByContent, + TransactionId *pFreezeXid, + MultiXactId *pCutoffMulti); static List *get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt); static List *get_tables_to_repack_partitioned(RepackCommand cmd, @@ -79,10 +172,45 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd, MemoryContext permcxt); static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid); + +static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt); +static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot, + ChangeContext *chgcxt); +static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple, + TupleTableSlot *ondisk_tuple, + ChangeContext *chgcxt); +static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot); +static void restore_tuple(BufFile *file, Relation relation, + TupleTableSlot *slot); +static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest, + TupleTableSlot *src); +static bool find_target_tuple(Relation rel, ChangeContext *chgcxt, + TupleTableSlot *locator, + TupleTableSlot *received); +static void process_concurrent_changes(XLogRecPtr end_of_wal, + ChangeContext *chgcxt, + bool done); +static void initialize_change_context(ChangeContext *chgcxt, + Relation relation, + Oid ident_index_id); +static void release_change_context(ChangeContext *chgcxt); +static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap, + Oid identIdx, + TransactionId frozenXid, + MultiXactId cutoffMulti); +static List *build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes); static Relation process_single_relation(RepackStmt *stmt, + LOCKMODE lockmode, + bool isTopLevel, ClusterParams *params); static Oid determine_clustered_index(Relation rel, bool usingindex, const char *indexname); + +static void start_repack_decoding_worker(Oid relid); +static void stop_repack_decoding_worker(void); +static Snapshot get_initial_snapshot(DecodingWorker *worker); + +static void ProcessRepackMessage(StringInfo msg); static const char *RepackCommandAsString(RepackCommand cmd); @@ -115,6 +243,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) ClusterParams params = {0}; Relation rel = NULL; MemoryContext repack_context; + LOCKMODE lockmode; List *rtcs; /* Parse option list */ @@ -125,6 +254,16 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) else if (strcmp(opt->defname, "analyze") == 0 || strcmp(opt->defname, "analyse") == 0) params.options |= defGetBoolean(opt) ? CLUOPT_ANALYZE : 0; + else if (strcmp(opt->defname, "concurrently") == 0 && + defGetBoolean(opt)) + { + if (stmt->command != REPACK_COMMAND_REPACK) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CONCURRENTLY option not supported for %s", + RepackCommandAsString(stmt->command))); + params.options |= CLUOPT_CONCURRENT; + } else ereport(ERROR, errcode(ERRCODE_SYNTAX_ERROR), @@ -134,13 +273,16 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) parser_errposition(pstate, opt->location)); } + /* Determine the lock mode to use. */ + lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0); + /* * If a single relation is specified, process it and we're done ... unless * the relation is a partitioned table, in which case we fall through. */ if (stmt->relation != NULL) { - rel = process_single_relation(stmt, ¶ms); + rel = process_single_relation(stmt, lockmode, isTopLevel, ¶ms); if (rel == NULL) return; /* all done */ } @@ -156,10 +298,29 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) "REPACK (ANALYZE)")); /* - * By here, we know we are in a multi-table situation. In order to avoid - * holding locks for too long, we want to process each table in its own - * transaction. This forces us to disallow running inside a user - * transaction block. + * By here, we know we are in a multi-table situation. + * + * Concurrent processing is currently considered rather special (e.g. in + * terms of resources consumed) so it is not performed in bulk. + */ + if (params.options & CLUOPT_CONCURRENT) + { + if (rel != NULL) + { + Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); + ereport(ERROR, + errmsg("REPACK CONCURRENTLY not supported for partitioned tables"), + errhint("Consider running the command for individual partitions.")); + } + else + ereport(ERROR, + errmsg("REPACK CONCURRENTLY requires explicit table name")); + } + + /* + * In order to avoid holding locks for too long, we want to process each + * table in its own transaction. This forces us to disallow running + * inside a user transaction block. */ PreventInTransactionBlock(isTopLevel, RepackCommandAsString(stmt->command)); @@ -168,6 +329,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) "Repack", ALLOCSET_DEFAULT_SIZES); + /* + * Since we open a new transaction for each relation, we have to check + * that the relation still is what we think it is. + * + * In single-transaction CLUSTER, we don't need the overhead. + */ params.options |= CLUOPT_RECHECK; /* @@ -253,7 +420,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) * Open the target table, coping with the case where it has been * dropped. */ - rel = try_table_open(rtc->tableOid, AccessExclusiveLock); + rel = try_table_open(rtc->tableOid, lockmode); if (rel == NULL) { CommitTransactionCommand(); @@ -264,7 +431,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) PushActiveSnapshot(GetTransactionSnapshot()); /* Process this table */ - cluster_rel(stmt->command, rel, rtc->indexOid, ¶ms); + cluster_rel(stmt->command, rel, rtc->indexOid, ¶ms, isTopLevel); /* cluster_rel closes the relation, but keeps lock */ PopActiveSnapshot(); @@ -278,6 +445,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) MemoryContextDelete(repack_context); } +/* + * In the non-concurrent case, we obtain AccessExclusiveLock throughout the + * operation to avoid any lock-upgrade hazards. In the concurrent case, we + * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the + * processing and only acquire AccessExclusiveLock at the end, to swap the + * relation -- supposedly for a short time. + */ +static LOCKMODE +RepackLockLevel(bool concurrent) +{ + if (concurrent) + return ShareUpdateExclusiveLock; + else + return AccessExclusiveLock; +} + /* * cluster_rel * @@ -293,22 +476,51 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) * If indexOid is InvalidOid, the table will be rewritten in physical order * instead of index order. * + * Note that, in the concurrent case, the function releases the lock at some + * point, in order to get AccessExclusiveLock for the final steps (i.e. to + * swap the relation files). To make things simpler, the caller should expect + * OldHeap to be closed on return, regardless CLUOPT_CONCURRENT. (The + * AccessExclusiveLock is kept till the end of the transaction.) + * * 'cmd' indicates which command is being executed, to be used for error * messages. */ void cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, - ClusterParams *params) + ClusterParams *params, bool isTopLevel) { Oid tableOid = RelationGetRelid(OldHeap); + Relation index; + LOCKMODE lmode; Oid save_userid; int save_sec_context; int save_nestlevel; bool verbose = ((params->options & CLUOPT_VERBOSE) != 0); bool recheck = ((params->options & CLUOPT_RECHECK) != 0); - Relation index; + bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0); + Oid ident_idx = InvalidOid; - Assert(CheckRelationLockedByMe(OldHeap, AccessExclusiveLock, false)); + /* Determine the lock mode to use. */ + lmode = RepackLockLevel(concurrent); + + /* + * Check some preconditions in the concurrent case. This also obtains the + * replica index OID. + */ + if (concurrent) + { + /* + * Make sure we're not in a transaction block. + * + * The reason is that repack_setup_logical_decoding() could deadlock + * if there's an XID already assigned. It would be possible to run in + * a transaction block if we had no XID, but this restriction is + * simpler for users to understand and we don't lose anything. + */ + PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)"); + + check_repack_concurrently_requirements(OldHeap, &ident_idx); + } /* Check for user-requested abort. */ CHECK_FOR_INTERRUPTS(); @@ -328,16 +540,15 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, RestrictSearchPath(); /* - * Since we may open a new transaction for each relation, we have to check - * that the relation still is what we think it is. + * Recheck that the relation is still what it was when we started. * - * If this is a single-transaction CLUSTER, we can skip these tests. We - * *must* skip the one on indisclustered since it would reject an attempt - * to cluster a not-previously-clustered index. + * Note that it's critical to skip this in single-relation CLUSTER; + * otherwise, we would reject an attempt to cluster using a + * not-previously-clustered index. */ if (recheck && !cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid, - params->options)) + lmode, params->options)) goto out; /* @@ -353,6 +564,12 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, errmsg("cannot execute %s on a shared catalog", RepackCommandAsString(cmd))); + /* + * The CONCURRENTLY case should have been rejected earlier because it does + * not support system catalogs. + */ + Assert(!(OldHeap->rd_rel->relisshared && concurrent)); + /* * Don't process temp tables of other backends ... their local buffer * manager is not going to cope. @@ -374,7 +591,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, if (OidIsValid(indexOid)) { /* verify the index is good and lock it */ - check_index_is_clusterable(OldHeap, indexOid, AccessExclusiveLock); + check_index_is_clusterable(OldHeap, indexOid, lmode); /* also open it */ index = index_open(indexOid, NoLock); } @@ -409,7 +626,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW && !RelationIsPopulated(OldHeap)) { - relation_close(OldHeap, AccessExclusiveLock); + if (index) + index_close(index, lmode); + relation_close(OldHeap, lmode); goto out; } @@ -422,11 +641,34 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, * invalid, because we move tuples around. Promote them to relation * locks. Predicate locks on indexes will be promoted when they are * reindexed. + * + * During concurrent processing, the heap as well as its indexes stay in + * operation, so we postpone this step until they are locked using + * AccessExclusiveLock near the end of the processing. */ - TransferPredicateLocksToHeapRelation(OldHeap); + if (!concurrent) + TransferPredicateLocksToHeapRelation(OldHeap); /* rebuild_relation does all the dirty work */ - rebuild_relation(OldHeap, index, verbose); + PG_TRY(); + { + rebuild_relation(OldHeap, index, verbose, ident_idx); + } + PG_FINALLY(); + { + if (concurrent) + { + /* + * Since during normal operation the worker was already asked to + * exit, stopping it explicitly is especially important on ERROR. + * However it still seems a good practice to make sure that the + * worker never survives the REPACK command. + */ + stop_repack_decoding_worker(); + } + } + PG_END_TRY(); + /* rebuild_relation closes OldHeap, and index if valid */ out: @@ -445,14 +687,14 @@ out: */ static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, - Oid userid, int options) + Oid userid, LOCKMODE lmode, int options) { Oid tableOid = RelationGetRelid(OldHeap); /* Check that the user still has privileges for the relation */ if (!repack_is_permitted_for_relation(cmd, tableOid, userid)) { - relation_close(OldHeap, AccessExclusiveLock); + relation_close(OldHeap, lmode); return false; } @@ -466,7 +708,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, */ if (RELATION_IS_OTHER_TEMP(OldHeap)) { - relation_close(OldHeap, AccessExclusiveLock); + relation_close(OldHeap, lmode); return false; } @@ -477,7 +719,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, */ if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid))) { - relation_close(OldHeap, AccessExclusiveLock); + relation_close(OldHeap, lmode); return false; } @@ -488,7 +730,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, if ((options & CLUOPT_RECHECK_ISCLUSTERED) != 0 && !get_index_isclustered(indexOid)) { - relation_close(OldHeap, AccessExclusiveLock); + relation_close(OldHeap, lmode); return false; } } @@ -500,7 +742,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, * Verify that the specified heap and index are valid to cluster on * * Side effect: obtains lock on the index. The caller may - * in some cases already have AccessExclusiveLock on the table, but + * in some cases already have a lock of the same strength on the table, but * not in all cases so we can't rely on the table-level lock for * protection here. */ @@ -626,17 +868,94 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal) } /* - * rebuild_relation: rebuild an existing relation in index or physical order + * Check if the CONCURRENTLY option is legal for the relation. * - * OldHeap: table to rebuild. - * index: index to cluster by, or NULL to rewrite in physical order. - * - * On entry, heap and index (if one is given) must be open, and - * AccessExclusiveLock held on them. - * On exit, they are closed, but locks on them are not released. + * *Ident_idx_p receives OID of the identity index. */ static void -rebuild_relation(Relation OldHeap, Relation index, bool verbose) +check_repack_concurrently_requirements(Relation rel, Oid *ident_idx_p) +{ + char relpersistence, + replident; + Oid ident_idx; + + /* Data changes in system relations are not logically decoded. */ + if (IsCatalogRelation(rel)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot repack relation \"%s\"", + RelationGetRelationName(rel)), + errhint("REPACK CONCURRENTLY is not supported for catalog relations.")); + + /* + * reorderbuffer.c does not seem to handle processing of TOAST relation + * alone. + */ + if (IsToastRelation(rel)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot repack relation \"%s\"", + RelationGetRelationName(rel)), + errhint("REPACK CONCURRENTLY is not supported for TOAST relations, unless the main relation is repacked too.")); + + relpersistence = rel->rd_rel->relpersistence; + if (relpersistence != RELPERSISTENCE_PERMANENT) + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot repack relation \"%s\"", + RelationGetRelationName(rel)), + errhint("REPACK CONCURRENTLY is only allowed for permanent relations.")); + + /* With NOTHING, WAL does not contain the old tuple. */ + replident = rel->rd_rel->relreplident; + if (replident == REPLICA_IDENTITY_NOTHING) + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot repack relation \"%s\"", + RelationGetRelationName(rel)), + errhint("Relation \"%s\" has insufficient replication identity.", + RelationGetRelationName(rel))); + + /* + * If the identity index is not set due to replica identity being, PK + * might exist. + */ + ident_idx = RelationGetReplicaIndex(rel); + if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex)) + ident_idx = rel->rd_pkindex; + if (!OidIsValid(ident_idx)) + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot process relation \"%s\"", + RelationGetRelationName(rel)), + errhint("Relation \"%s\" has no identity index.", + RelationGetRelationName(rel))); + + *ident_idx_p = ident_idx; +} + + +/* + * rebuild_relation: rebuild an existing relation in index or physical order + * + * OldHeap: table to rebuild. See cluster_rel() for comments on the required + * lock strength. + * + * index: index to cluster by, or NULL to rewrite in physical order. + * + * ident_idx: identity index, to handle replaying of concurrent data changes + * to the new heap. InvalidOid if there's no CONCURRENTLY option. + * + * On entry, heap and index (if one is given) must be open, and the + * appropriate lock held on them -- AccessExclusiveLock for exclusive + * processing and ShareUpdateExclusiveLock for concurrent processing. + * + * On exit, they are closed, but still locked with AccessExclusiveLock. + * (The function handles the lock upgrade if 'concurrent' is true.) + */ +static void +rebuild_relation(Relation OldHeap, Relation index, bool verbose, + Oid ident_idx) { Oid tableOid = RelationGetRelid(OldHeap); Oid accessMethod = OldHeap->rd_rel->relam; @@ -644,13 +963,55 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose) Oid OIDNewHeap; Relation NewHeap; char relpersistence; - bool is_system_catalog; bool swap_toast_by_content; TransactionId frozenXid; MultiXactId cutoffMulti; + bool concurrent = OidIsValid(ident_idx); + Snapshot snapshot = NULL; +#if USE_ASSERT_CHECKING + LOCKMODE lmode; - Assert(CheckRelationLockedByMe(OldHeap, AccessExclusiveLock, false) && - (index == NULL || CheckRelationLockedByMe(index, AccessExclusiveLock, false))); + lmode = concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock; + + Assert(CheckRelationLockedByMe(OldHeap, lmode, false)); + Assert(index == NULL || CheckRelationLockedByMe(index, lmode, false)); +#endif + + if (concurrent) + { + /* + * The worker needs to be member of the locking group we're the leader + * of. We ought to become the leader before the worker starts. The + * worker will join the group as soon as it starts. + * + * This is to make sure that the deadlock described below is + * detectable by deadlock.c: if the worker waits for a transaction to + * complete and we are waiting for the worker output, then effectively + * we (i.e. this backend) are waiting for that transaction. + */ + BecomeLockGroupLeader(); + + /* + * Start the worker that decodes data changes applied while we're + * copying the table contents. + * + * Note that the worker has to wait for all transactions with XID + * already assigned to finish. If some of those transactions is + * waiting for a lock conflicting with ShareUpdateExclusiveLock on our + * table (e.g. it runs CREATE INDEX), we can end up in a deadlock. + * Not sure this risk is worth unlocking/locking the table (and its + * clustering index) and checking again if it's still eligible for + * REPACK CONCURRENTLY. + */ + start_repack_decoding_worker(tableOid); + + /* + * Wait until the worker has the initial snapshot and retrieve it. + */ + snapshot = get_initial_snapshot(decoding_worker); + + PushActiveSnapshot(snapshot); + } /* for CLUSTER or REPACK USING INDEX, mark the index as the one to use */ if (index != NULL) @@ -658,7 +1019,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose) /* Remember info about rel before closing OldHeap */ relpersistence = OldHeap->rd_rel->relpersistence; - is_system_catalog = IsSystemRelation(OldHeap); /* * Create the transient table that will receive the re-ordered data. @@ -674,30 +1034,59 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose) NewHeap = table_open(OIDNewHeap, NoLock); /* Copy the heap data into the new table in the desired order */ - copy_table_data(NewHeap, OldHeap, index, verbose, + copy_table_data(NewHeap, OldHeap, index, snapshot, verbose, &swap_toast_by_content, &frozenXid, &cutoffMulti); + /* The historic snapshot won't be needed anymore. */ + if (snapshot) + { + PopActiveSnapshot(); + UpdateActiveSnapshotCommandId(); + } - /* Close relcache entries, but keep lock until transaction commit */ - table_close(OldHeap, NoLock); - if (index) - index_close(index, NoLock); + if (concurrent) + { + Assert(!swap_toast_by_content); - /* - * Close the new relation so it can be dropped as soon as the storage is - * swapped. The relation is not visible to others, so no need to unlock it - * explicitly. - */ - table_close(NewHeap, NoLock); + /* + * Close the index, but keep the lock. Both heaps will be closed by + * the following call. + */ + if (index) + index_close(index, NoLock); - /* - * Swap the physical files of the target and transient tables, then - * rebuild the target's indexes and throw away the transient table. - */ - finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog, - swap_toast_by_content, false, true, - frozenXid, cutoffMulti, - relpersistence); + rebuild_relation_finish_concurrent(NewHeap, OldHeap, ident_idx, + frozenXid, cutoffMulti); + + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_FINAL_CLEANUP); + } + else + { + bool is_system_catalog = IsSystemRelation(OldHeap); + + /* Close relcache entries, but keep lock until transaction commit */ + table_close(OldHeap, NoLock); + if (index) + index_close(index, NoLock); + + /* + * Close the new relation so it can be dropped as soon as the storage + * is swapped. The relation is not visible to others, so no need to + * unlock it explicitly. + */ + table_close(NewHeap, NoLock); + + /* + * Swap the physical files of the target and transient tables, then + * rebuild the target's indexes and throw away the transient table. + */ + finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog, + swap_toast_by_content, false, true, + true, /* reindex */ + frozenXid, cutoffMulti, + relpersistence); + } } @@ -832,15 +1221,18 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, /* * Do the physical copying of table data. * + * 'snapshot' and 'decoding_ctx': see table_relation_copy_for_cluster(). Pass + * iff concurrent processing is required. + * * There are three output parameters: * *pSwapToastByContent is set true if toast tables must be swapped by content. * *pFreezeXid receives the TransactionId used as freeze cutoff point. * *pCutoffMulti receives the MultiXactId used as a cutoff point. */ static void -copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verbose, - bool *pSwapToastByContent, TransactionId *pFreezeXid, - MultiXactId *pCutoffMulti) +copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, + Snapshot snapshot, bool verbose, bool *pSwapToastByContent, + TransactionId *pFreezeXid, MultiXactId *pCutoffMulti) { Relation relRelation; HeapTuple reltup; @@ -857,6 +1249,10 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb int elevel = verbose ? INFO : DEBUG2; PGRUsage ru0; char *nspname; + bool concurrent = snapshot != NULL; + LOCKMODE lmode; + + lmode = concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock; pg_rusage_init(&ru0); @@ -885,7 +1281,7 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb * will be held till end of transaction. */ if (OldHeap->rd_rel->reltoastrelid) - LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock); + LockRelationOid(OldHeap->rd_rel->reltoastrelid, lmode); /* * If both tables have TOAST tables, perform toast swap by content. It is @@ -894,7 +1290,8 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb * swap by links. This is okay because swap by content is only essential * for system catalogs, and we don't support schema changes for them. */ - if (OldHeap->rd_rel->reltoastrelid && NewHeap->rd_rel->reltoastrelid) + if (OldHeap->rd_rel->reltoastrelid && NewHeap->rd_rel->reltoastrelid && + !concurrent) { *pSwapToastByContent = true; @@ -915,6 +1312,10 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb * follow the toast pointers to the wrong place. (It would actually * work for values copied over from the old toast table, but not for * any values that we toast which were previously not toasted.) + * + * This would not work with CONCURRENTLY because we may need to delete + * TOASTed tuples from the new heap. With this hack, we'd delete them + * from the old heap. */ NewHeap->rd_toastoid = OldHeap->rd_rel->reltoastrelid; } @@ -990,7 +1391,8 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb * values (e.g. because the AM doesn't use freezing). */ table_relation_copy_for_cluster(OldHeap, NewHeap, OldIndex, use_sort, - cutoffs.OldestXmin, &cutoffs.FreezeLimit, + cutoffs.OldestXmin, snapshot, + &cutoffs.FreezeLimit, &cutoffs.MultiXactCutoff, &num_tuples, &tups_vacuumed, &tups_recently_dead); @@ -999,7 +1401,11 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb *pFreezeXid = cutoffs.FreezeLimit; *pCutoffMulti = cutoffs.MultiXactCutoff; - /* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */ + /* + * Reset rd_toastoid just to be tidy --- it shouldn't be looked at again. + * In the CONCURRENTLY case, we need to set it again before applying the + * concurrent changes. + */ NewHeap->rd_toastoid = InvalidOid; num_pages = RelationGetNumberOfBlocks(NewHeap); @@ -1457,14 +1863,13 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool swap_toast_by_content, bool check_constraints, bool is_internal, + bool reindex, TransactionId frozenXid, MultiXactId cutoffMulti, char newrelpersistence) { ObjectAddress object; Oid mapped_tables[4]; - int reindex_flags; - ReindexParams reindex_params = {0}; int i; /* Report that we are now swapping relation files */ @@ -1490,39 +1895,47 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, if (is_system_catalog) CacheInvalidateCatalog(OIDOldHeap); - /* - * Rebuild each index on the relation (but not the toast table, which is - * all-new at this point). It is important to do this before the DROP - * step because if we are processing a system catalog that will be used - * during DROP, we want to have its indexes available. There is no - * advantage to the other order anyway because this is all transactional, - * so no chance to reclaim disk space before commit. We do not need a - * final CommandCounterIncrement() because reindex_relation does it. - * - * Note: because index_build is called via reindex_relation, it will never - * set indcheckxmin true for the indexes. This is OK even though in some - * sense we are building new indexes rather than rebuilding existing ones, - * because the new heap won't contain any HOT chains at all, let alone - * broken ones, so it can't be necessary to set indcheckxmin. - */ - reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE; - if (check_constraints) - reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS; + if (reindex) + { + int reindex_flags; + ReindexParams reindex_params = {0}; - /* - * Ensure that the indexes have the same persistence as the parent - * relation. - */ - if (newrelpersistence == RELPERSISTENCE_UNLOGGED) - reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED; - else if (newrelpersistence == RELPERSISTENCE_PERMANENT) - reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT; + /* + * Rebuild each index on the relation (but not the toast table, which + * is all-new at this point). It is important to do this before the + * DROP step because if we are processing a system catalog that will + * be used during DROP, we want to have its indexes available. There + * is no advantage to the other order anyway because this is all + * transactional, so no chance to reclaim disk space before commit. We + * do not need a final CommandCounterIncrement() because + * reindex_relation does it. + * + * Note: because index_build is called via reindex_relation, it will + * never set indcheckxmin true for the indexes. This is OK even + * though in some sense we are building new indexes rather than + * rebuilding existing ones, because the new heap won't contain any + * HOT chains at all, let alone broken ones, so it can't be necessary + * to set indcheckxmin. + */ + reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE; + if (check_constraints) + reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS; - /* Report that we are now reindexing relations */ - pgstat_progress_update_param(PROGRESS_REPACK_PHASE, - PROGRESS_REPACK_PHASE_REBUILD_INDEX); + /* + * Ensure that the indexes have the same persistence as the parent + * relation. + */ + if (newrelpersistence == RELPERSISTENCE_UNLOGGED) + reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED; + else if (newrelpersistence == RELPERSISTENCE_PERMANENT) + reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT; - reindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params); + /* Report that we are now reindexing relations */ + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_REBUILD_INDEX); + + reindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params); + } /* Report that we are now doing clean up */ pgstat_progress_update_param(PROGRESS_REPACK_PHASE, @@ -1566,6 +1979,17 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, object.objectId = OIDNewHeap; object.objectSubId = 0; + if (!reindex) + { + /* + * Make sure the changes in pg_class are visible. This is especially + * important if !swap_toast_by_content, so that the correct TOAST + * relation is dropped. (reindex_relation() above did not help in this + * case)) + */ + CommandCounterIncrement(); + } + /* * The new relation is local to our transaction and we know nothing * depends on it, so DROP_RESTRICT should be OK. @@ -1605,7 +2029,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, /* Get the associated valid index to be renamed */ toastidx = toast_get_valid_index(newrel->rd_rel->reltoastrelid, - NoLock); + AccessExclusiveLock); /* rename the toast table ... */ snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u", @@ -1876,7 +2300,8 @@ repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) * case, if an index name is given, it's up to the caller to resolve it. */ static Relation -process_single_relation(RepackStmt *stmt, ClusterParams *params) +process_single_relation(RepackStmt *stmt, LOCKMODE lockmode, bool isTopLevel, + ClusterParams *params) { Relation rel; Oid tableOid; @@ -1893,13 +2318,9 @@ process_single_relation(RepackStmt *stmt, ClusterParams *params) errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("ANALYZE option must be specified when a column list is provided")); - /* - * Find, lock, and check permissions on the table. We obtain - * AccessExclusiveLock right away to avoid lock-upgrade hazard in the - * single-transaction case. - */ + /* Find, lock, and check permissions on the table. */ tableOid = RangeVarGetRelidExtended(stmt->relation->relation, - AccessExclusiveLock, + lockmode, 0, RangeVarCallbackMaintainsTable, NULL); @@ -1924,13 +2345,14 @@ process_single_relation(RepackStmt *stmt, ClusterParams *params) return rel; else { - Oid indexOid; + Oid indexOid = InvalidOid; indexOid = determine_clustered_index(rel, stmt->usingindex, stmt->indexname); if (OidIsValid(indexOid)) - check_index_is_clusterable(rel, indexOid, AccessExclusiveLock); - cluster_rel(stmt->command, rel, indexOid, params); + check_index_is_clusterable(rel, indexOid, lockmode); + + cluster_rel(stmt->command, rel, indexOid, params, isTopLevel); /* * Do an analyze, if requested. We close the transaction and start a @@ -2025,3 +2447,1182 @@ RepackCommandAsString(RepackCommand cmd) } return "???"; /* keep compiler quiet */ } + +/* + * Apply all the changes stored in 'file'. + */ +static void +apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt) +{ + ConcurrentChangeKind kind = '\0'; + Relation rel = chgcxt->cc_rel; + TupleTableSlot *spilled_tuple; + TupleTableSlot *old_update_tuple; + TupleTableSlot *ondisk_tuple; + MemoryContext apply_cxt; + bool have_old_tuple = false; + + /* + * Use a separate memory context for the tuples and any memory needed to + * process them. + * + * XXX would this be better with GenerationContextCreate? + */ + apply_cxt = AllocSetContextCreate(TopTransactionContext, + "REPACK - apply", + ALLOCSET_DEFAULT_SIZES); + + spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel), + &TTSOpsVirtual); + ondisk_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel), + table_slot_callbacks(rel)); + old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel), + &TTSOpsVirtual); + + while (true) + { + size_t nread; + ConcurrentChangeKind prevkind = kind; + + CHECK_FOR_INTERRUPTS(); + + nread = BufFileReadMaybeEOF(file, &kind, 1, true); + if (nread == 0) /* done with the file? */ + break; + + /* + * If this is the old tuple for an update, read it into the tuple slot + * and go to the next one. The update itself will be executed on the + * next iteration, when we receive the NEW tuple. + */ + if (kind == CHANGE_UPDATE_OLD) + { + restore_tuple(file, rel, old_update_tuple); + have_old_tuple = true; + continue; + } + + /* + * Just before an UPDATE or DELETE, we must update the command + * counter, because the change could refer to a tuple that we have + * just inserted; and before an INSERT, we have to do this also if the + * previous command was either update or delete. + * + * With this approach we don't spend so many CCIs for long strings of + * only INSERTs, which can't affect one another. + */ + if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE || + (kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW || + prevkind == CHANGE_DELETE))) + { + CommandCounterIncrement(); + UpdateActiveSnapshotCommandId(); + } + + /* + * Now restore the tuple into the slot and execute the change. + */ + restore_tuple(file, rel, spilled_tuple); + + if (kind == CHANGE_INSERT) + { + apply_concurrent_insert(rel, spilled_tuple, chgcxt); + } + else if (kind == CHANGE_DELETE) + { + bool found; + + /* Find the tuple to be deleted */ + found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple); + if (!found) + elog(ERROR, "failed to find target tuple"); + apply_concurrent_delete(rel, ondisk_tuple); + } + else if (kind == CHANGE_UPDATE_NEW) + { + TupleTableSlot *key; + bool found; + + if (have_old_tuple) + key = old_update_tuple; + else + key = spilled_tuple; + + /* Find the tuple to be updated or deleted. */ + found = find_target_tuple(rel, chgcxt, key, ondisk_tuple); + if (!found) + elog(ERROR, "failed to find target tuple"); + + /* + * If 'tup' contains TOAST pointers, they point to the old + * relation's toast. Copy the corresponding TOAST pointers for the + * new relation from the existing tuple. (The fact that we + * received a TOAST pointer here implies that the attribute hasn't + * changed.) + */ + adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple); + + apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt); + + ExecClearTuple(old_update_tuple); + have_old_tuple = false; + } + else + elog(ERROR, "unrecognized kind of change: %d", kind); + + MemoryContextReset(apply_cxt); + } + + /* Cleanup. */ + ExecDropSingleTupleTableSlot(spilled_tuple); + ExecDropSingleTupleTableSlot(ondisk_tuple); + ExecDropSingleTupleTableSlot(old_update_tuple); + MemoryContextDelete(apply_cxt); +} + +/* + * Apply an insert from the spill of concurrent changes to the new copy of the + * table. + */ +static void +apply_concurrent_insert(Relation rel, TupleTableSlot *slot, + ChangeContext *chgcxt) +{ + List *recheck; + + /* Put the tuple in the table, but make sure it won't be decoded */ + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + HEAP_INSERT_NO_LOGICAL, NULL); + + /* + * Update indexes with this new tuple. Because we're merely replaying an + * action that already happened, we have no use for the recheck list of + * indexes returned, so just free it. XXX or maybe just leave it? + */ + recheck = ExecInsertIndexTuples(chgcxt->cc_rri, + chgcxt->cc_estate, + 0, + slot, + NIL, NULL); + list_free(recheck); + + pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1); + + ResetPerTupleExprContext(chgcxt->cc_estate); +} + +/* + * Apply an update from the spill of concurrent changes to the new copy of the + * table. + */ +static void +apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple, + TupleTableSlot *ondisk_tuple, + ChangeContext *chgcxt) +{ + LockTupleMode lockmode; + TM_FailureData tmfd; + TU_UpdateIndexes update_indexes; + TM_Result res; + List *recheck; + + /* + * Carry out the update, avoiding logical decoding info. + */ + res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple, + GetCurrentCommandId(true), + InvalidSnapshot, + InvalidSnapshot, + TABLE_UPDATE_NO_LOGICAL, + &tmfd, &lockmode, &update_indexes); + if (res != TM_Ok) + ereport(ERROR, + errmsg("failed to apply concurrent UPDATE")); + + if (update_indexes != TU_None) + { + bits32 flags = EIIT_IS_UPDATE; + + if (update_indexes == TU_Summarizing) + flags |= EIIT_ONLY_SUMMARIZING; + recheck = ExecInsertIndexTuples(chgcxt->cc_rri, + chgcxt->cc_estate, + flags, + spilled_tuple, + NIL, NULL); + list_free(recheck); + ResetPerTupleExprContext(chgcxt->cc_estate); + } + + pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1); +} + +static void +apply_concurrent_delete(Relation rel, TupleTableSlot *slot) +{ + TM_Result res; + TM_FailureData tmfd; + + /* + * Delete tuple from the new heap. + * + * Do it like in simple_heap_delete(), except for 'wal_logical' (and + * except for 'wait'). + */ + res = table_tuple_delete(rel, &(slot->tts_tid), + GetCurrentCommandId(true), + InvalidSnapshot, InvalidSnapshot, + TABLE_DELETE_NO_LOGICAL, + &tmfd); + + if (res != TM_Ok) + ereport(ERROR, + errmsg("failed to apply concurrent DELETE")); + + pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_DELETED, 1); +} + +/* + * Read tuple from file and put it in the input slot. + * + * External attributes are stored in separate memory chunks, in order to avoid + * exceeding MaxAllocSize - that could happen if the individual attributes are + * smaller than MaxAllocSize but the whole tuple is bigger. + */ +static void +restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot) +{ + uint32 t_len; + HeapTuple tup; + MemoryContext oldcxt; + int natt_ext; + + oldcxt = MemoryContextSwitchTo(slot->tts_mcxt); + + /* Read the tuple. */ + BufFileReadExact(file, &t_len, sizeof(t_len)); + tup = (HeapTuple) palloc(HEAPTUPLESIZE + t_len); + tup->t_data = (HeapTupleHeader) ((char *) tup + HEAPTUPLESIZE); + BufFileReadExact(file, tup->t_data, t_len); + tup->t_len = t_len; + ItemPointerSetInvalid(&tup->t_self); + tup->t_tableOid = RelationGetRelid(relation); + + /* + * Put the tuple we read in a slot. This deforms it, so that we can hack + * the external attributes in place. + */ + ExecForceStoreHeapTuple(tup, slot, false); + + /* + * Next, read any attributes we stored separately into the tts_values + * array elements expecting them, if any. This matches + * repack_store_change. + */ + BufFileReadExact(file, &natt_ext, sizeof(natt_ext)); + if (natt_ext > 0) + { + TupleDesc desc = slot->tts_tupleDescriptor; + + for (int i = 0; i < desc->natts; i++) + { + CompactAttribute *attr = TupleDescCompactAttr(desc, i); + varlena *varlen; + union + { + alignas(int32) varlena hdr; + char data[sizeof(void *)]; + } chunk_header; + void *value; + Size varlensz; + + if (attr->attisdropped || attr->attlen != -1) + continue; + if (slot_attisnull(slot, i + 1)) + continue; + varlen = (varlena *) DatumGetPointer(slot->tts_values[i]); + if (!VARATT_IS_EXTERNAL(varlen)) + continue; + slot_getsomeattrs(slot, i + 1); + + BufFileReadExact(file, &chunk_header, VARHDRSZ); + varlensz = VARSIZE_ANY(&chunk_header); + + value = palloc(varlensz); + SET_VARSIZE(value, VARSIZE_ANY(&chunk_header)); + BufFileReadExact(file, (char *) value + VARHDRSZ, varlensz - VARHDRSZ); + + slot->tts_values[i] = PointerGetDatum(value); + natt_ext--; + } + } + + MemoryContextSwitchTo(oldcxt); +} + +/* + * Adjust 'dest' replacing any EXTERNAL_ONDISK toast pointers with the + * corresponding ones from 'src'. + */ +static void +adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *src) +{ + TupleDesc desc = dest->tts_tupleDescriptor; + + for (int i = 0; i < desc->natts; i++) + { + CompactAttribute *attr = TupleDescCompactAttr(desc, i); + varlena *varlena_dst; + + if (attr->attisdropped) + continue; + if (attr->attlen != -1) + continue; + if (slot_attisnull(dest, i + 1)) + continue; + + slot_getsomeattrs(dest, i + 1); + + varlena_dst = (varlena *) DatumGetPointer(dest->tts_values[i]); + if (!VARATT_IS_EXTERNAL_ONDISK(varlena_dst)) + continue; + slot_getsomeattrs(src, i + 1); + + /* + * XXX We simply replace the pointer to the Datum from the other one, + * which is probably bogus. + */ + dest->tts_values[i] = src->tts_values[i]; + } +} + +/* + * Find the tuple to be updated or deleted by the given data change, whose + * tuple has already been loaded into locator. + * + * If the tuple is found, put it in retrieved and return true. If the tuple is + * not found, return false. + */ +static bool +find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator, + TupleTableSlot *retrieved) +{ + Form_pg_index idx = chgcxt->cc_ident_index->rd_index; + IndexScanDesc scan; + bool retval; + + /* + * Scan key is passed by caller, so it does not have to be constructed + * multiple times. Key entries have all fields initialized, except for + * sk_argument. + * + * Use the incoming tuple to finalize the scan key. + */ + for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++) + { + ScanKey entry = &chgcxt->cc_ident_key[i]; + AttrNumber attno = idx->indkey.values[i]; + + entry->sk_argument = locator->tts_values[attno - 1]; + Assert(!locator->tts_isnull[attno - 1]); + } + + /* XXX no instrumentation for now */ + scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(), + NULL, chgcxt->cc_ident_key_nentries, 0); + index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0); + retval = index_getnext_slot(scan, ForwardScanDirection, retrieved); + index_endscan(scan); + + return retval; +} + +/* + * Decode and apply concurrent changes, up to (and including) the record whose + * LSN is 'end_of_wal'. + * + * XXX the names "process_concurrent_changes" and "apply_concurrent_changes" + * are far too similar to each other. + */ +static void +process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done) +{ + DecodingWorkerShared *shared; + char fname[MAXPGPATH]; + BufFile *file; + + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_CATCH_UP); + + /* Ask the worker for the file. */ + shared = (DecodingWorkerShared *) dsm_segment_address(decoding_worker->seg); + SpinLockAcquire(&shared->mutex); + shared->lsn_upto = end_of_wal; + shared->done = done; + SpinLockRelease(&shared->mutex); + + /* + * The worker needs to finish processing of the current WAL record. Even + * if it's idle, it'll need to close the output file. Thus we're likely to + * wait, so prepare for sleep. + */ + ConditionVariablePrepareToSleep(&shared->cv); + for (;;) + { + int last_exported; + + SpinLockAcquire(&shared->mutex); + last_exported = shared->last_exported; + SpinLockRelease(&shared->mutex); + + /* + * Has the worker exported the file we are waiting for? + */ + if (last_exported == chgcxt->cc_file_seq) + break; + + ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT); + } + ConditionVariableCancelSleep(); + + /* Open the file. */ + DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq); + file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false); + apply_concurrent_changes(file, chgcxt); + + BufFileClose(file); + + /* Get ready for the next file. */ + chgcxt->cc_file_seq++; +} + +/* + * Initialize the ChangeContext struct for the given relation, with + * the given index as identity index. + */ +static void +initialize_change_context(ChangeContext *chgcxt, + Relation relation, Oid ident_index_id) +{ + chgcxt->cc_rel = relation; + + /* Only initialize fields needed by ExecInsertIndexTuples(). */ + chgcxt->cc_estate = CreateExecutorState(); + + chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo)); + InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0); + ExecOpenIndices(chgcxt->cc_rri, false); + + /* + * The table's relcache entry already has the relcache entry for the + * identity index; find that. + */ + chgcxt->cc_ident_index = NULL; + for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++) + { + Relation ind_rel; + + ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i]; + if (ind_rel->rd_id == ident_index_id) + { + chgcxt->cc_ident_index = ind_rel; + break; + } + } + if (chgcxt->cc_ident_index == NULL) + elog(ERROR, "failed to find identity index"); + + /* Set up for scanning said identity index */ + { + Form_pg_index indexForm; + + indexForm = chgcxt->cc_ident_index->rd_index; + chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts; + chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts); + for (int i = 0; i < indexForm->indnkeyatts; i++) + { + ScanKey entry; + Oid opfamily, + opcintype, + opno, + opcode; + + entry = &chgcxt->cc_ident_key[i]; + + opfamily = chgcxt->cc_ident_index->rd_opfamily[i]; + opcintype = chgcxt->cc_ident_index->rd_opcintype[i]; + opno = get_opfamily_member(opfamily, opcintype, opcintype, + BTEqualStrategyNumber); + if (!OidIsValid(opno)) + elog(ERROR, "failed to find = operator for type %u", opcintype); + opcode = get_opcode(opno); + if (!OidIsValid(opcode)) + elog(ERROR, "failed to find = operator for operator %u", opno); + + /* Initialize everything but argument. */ + ScanKeyInit(entry, + i + 1, + BTEqualStrategyNumber, opcode, + (Datum) NULL); + entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i]; + } + } + + chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1; +} + +/* + * Free up resources taken by a ChangeContext. + */ +static void +release_change_context(ChangeContext *chgcxt) +{ + ExecCloseIndices(chgcxt->cc_rri); + FreeExecutorState(chgcxt->cc_estate); + /* XXX are these pfrees necessary? */ + pfree(chgcxt->cc_rri); + pfree(chgcxt->cc_ident_key); +} + +/* + * The final steps of rebuild_relation() for concurrent processing. + * + * On entry, NewHeap is locked in AccessExclusiveLock mode. OldHeap and its + * clustering index (if one is passed) are still locked in a mode that allows + * concurrent data changes. On exit, both tables and their indexes are closed, + * but locked in AccessExclusiveLock mode. + */ +static void +rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap, + Oid identIdx, TransactionId frozenXid, + MultiXactId cutoffMulti) +{ + LOCKMODE lockmode_old PG_USED_FOR_ASSERTS_ONLY; + List *ind_oids_new; + Oid old_table_oid = RelationGetRelid(OldHeap); + Oid new_table_oid = RelationGetRelid(NewHeap); + List *ind_oids_old = RelationGetIndexList(OldHeap); + ListCell *lc, + *lc2; + char relpersistence; + bool is_system_catalog; + Oid ident_idx_new; + XLogRecPtr wal_insert_ptr, + end_of_wal; + char dummy_rec_data = '\0'; + Relation *ind_refs, + *ind_refs_p; + int nind; + ChangeContext chgcxt; + + /* Like in cluster_rel(). */ + lockmode_old = ShareUpdateExclusiveLock; + Assert(CheckRelationLockedByMe(OldHeap, lockmode_old, false)); + /* This is expected from the caller. */ + Assert(CheckRelationLockedByMe(NewHeap, AccessExclusiveLock, false)); + + /* + * Unlike the exclusive case, we build new indexes for the new relation + * rather than swapping the storage and reindexing the old relation. The + * point is that the index build can take some time, so we do it before we + * get AccessExclusiveLock on the old heap and therefore we cannot swap + * the heap storage yet. + * + * index_create() will lock the new indexes using AccessExclusiveLock - no + * need to change that. At the same time, we use ShareUpdateExclusiveLock + * to lock the existing indexes - that should be enough to prevent others + * from changing them while we're repacking the relation. The lock on + * table should prevent others from changing the index column list, but + * might not be enough for commands like ALTER INDEX ... SET ... (Those + * are not necessarily dangerous, but can make user confused if the + * changes they do get lost due to REPACK.) + */ + ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old); + + /* Find "identity index" on the new relation. */ + ident_idx_new = InvalidOid; + forboth(lc, ind_oids_old, lc2, ind_oids_new) + { + Oid ind_old = lfirst_oid(lc); + Oid ind_new = lfirst_oid(lc2); + + if (identIdx == ind_old) + { + ident_idx_new = ind_new; + break; + } + } + + /* Should not happen, given our lock on the old relation. */ + if (!OidIsValid(ident_idx_new)) + ereport(ERROR, + errmsg("identity index missing on the new relation")); + + /* Gather information to apply concurrent changes. */ + initialize_change_context(&chgcxt, NewHeap, ident_idx_new); + + /* + * During testing, wait for another backend to perform concurrent data + * changes which we will process below. + */ + INJECTION_POINT("repack-concurrently-before-lock", NULL); + + /* + * Flush all WAL records inserted so far (possibly except for the last + * incomplete page; see GetInsertRecPtr), to minimize the amount of data + * we need to flush while holding exclusive lock on the source table. + */ + wal_insert_ptr = GetInsertRecPtr(); + XLogFlush(wal_insert_ptr); + end_of_wal = GetFlushRecPtr(NULL); + + /* + * Apply concurrent changes first time, to minimize the time we need to + * hold AccessExclusiveLock. (Quite some amount of WAL could have been + * written during the data copying and index creation.) + */ + process_concurrent_changes(end_of_wal, &chgcxt, false); + + /* + * Acquire AccessExclusiveLock on the table, its TOAST relation (if there + * is one), all its indexes, so that we can swap the files. + */ + LockRelationOid(old_table_oid, AccessExclusiveLock); + + /* + * Lock all indexes now, not only the clustering one: all indexes need to + * have their files swapped. While doing that, store their relation + * references in an array, to handle predicate locks below. + */ + ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old)); + nind = 0; + foreach_oid(ind_oid, ind_oids_old) + { + Relation index; + + index = index_open(ind_oid, AccessExclusiveLock); + + /* + * TODO 1) Do we need to check if ALTER INDEX was executed since the + * new index was created in build_new_indexes()? 2) Specifically for + * the clustering index, should check_index_is_clusterable() be called + * here? (Not sure about the latter: ShareUpdateExclusiveLock on the + * table probably blocks all commands that affect the result of + * check_index_is_clusterable().) + */ + *ind_refs_p = index; + ind_refs_p++; + nind++; + } + + /* + * Lock the OldHeap's TOAST relation exclusively - again, the lock is + * needed to swap the files. + */ + if (OidIsValid(OldHeap->rd_rel->reltoastrelid)) + LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock); + + /* + * Tuples and pages of the old heap will be gone, but the heap will stay. + */ + TransferPredicateLocksToHeapRelation(OldHeap); + /* The same for indexes. */ + for (int i = 0; i < nind; i++) + { + Relation index = ind_refs[i]; + + TransferPredicateLocksToHeapRelation(index); + + /* + * References to indexes on the old relation are not needed anymore, + * however locks stay till the end of the transaction. + */ + index_close(index, NoLock); + } + pfree(ind_refs); + + /* + * Flush anything we see in WAL, to make sure that all changes committed + * while we were waiting for the exclusive lock are available for + * decoding. This should not be necessary if all backends had + * synchronous_commit set, but we can't rely on this setting. + * + * Unfortunately, GetInsertRecPtr() may lag behind the actual insert + * position, and GetLastImportantRecPtr() points at the start of the last + * record rather than at the end. Thus the simplest way to determine the + * insert position is to insert a dummy record and use its LSN. + * + * XXX Consider using GetLastImportantRecPtr() and adding the size of the + * last record (plus the total size of all the page headers the record + * spans)? + */ + XLogBeginInsert(); + XLogRegisterData(&dummy_rec_data, 1); + wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP); + XLogFlush(wal_insert_ptr); + end_of_wal = GetFlushRecPtr(NULL); + + /* + * Apply the concurrent changes again. Indicate that the decoding worker + * won't be needed anymore. + */ + process_concurrent_changes(end_of_wal, &chgcxt, true); + + /* Remember info about rel before closing OldHeap */ + relpersistence = OldHeap->rd_rel->relpersistence; + is_system_catalog = IsSystemRelation(OldHeap); + + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_SWAP_REL_FILES); + + /* + * Even ShareUpdateExclusiveLock should have prevented others from + * creating / dropping indexes (even using the CONCURRENTLY option), so we + * do not need to check whether the lists match. + */ + forboth(lc, ind_oids_old, lc2, ind_oids_new) + { + Oid ind_old = lfirst_oid(lc); + Oid ind_new = lfirst_oid(lc2); + Oid mapped_tables[4]; + + /* Zero out possible results from swapped_relation_files */ + memset(mapped_tables, 0, sizeof(mapped_tables)); + + swap_relation_files(ind_old, ind_new, + (old_table_oid == RelationRelationId), + false, /* swap_toast_by_content */ + true, + InvalidTransactionId, + InvalidMultiXactId, + mapped_tables); + +#ifdef USE_ASSERT_CHECKING + + /* + * Concurrent processing is not supported for system relations, so + * there should be no mapped tables. + */ + for (int i = 0; i < 4; i++) + Assert(mapped_tables[i] == 0); +#endif + } + + /* The new indexes must be visible for deletion. */ + CommandCounterIncrement(); + + /* Close the old heap but keep lock until transaction commit. */ + table_close(OldHeap, NoLock); + /* Close the new heap. (We didn't have to open its indexes). */ + table_close(NewHeap, NoLock); + + /* Cleanup what we don't need anymore. (And close the identity index.) */ + release_change_context(&chgcxt); + + /* + * Swap the relations and their TOAST relations and TOAST indexes. This + * also drops the new relation and its indexes. + * + * (System catalogs are currently not supported.) + */ + Assert(!is_system_catalog); + finish_heap_swap(old_table_oid, new_table_oid, + is_system_catalog, + false, /* swap_toast_by_content */ + false, + true, + false, /* reindex */ + frozenXid, cutoffMulti, + relpersistence); +} + +/* + * Build indexes on NewHeap according to those on OldHeap. + * + * OldIndexes is the list of index OIDs on OldHeap. The contained indexes end + * up locked using ShareUpdateExclusiveLock. + * + * A list of OIDs of the corresponding indexes created on NewHeap is + * returned. The order of items does match, so we can use these arrays to swap + * index storage. + */ +static List * +build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes) +{ + List *result = NIL; + + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_REBUILD_INDEX); + + foreach_oid(ind_oid, OldIndexes) + { + Oid ind_oid_new; + char *newName; + Relation ind; + + ind = index_open(ind_oid, ShareUpdateExclusiveLock); + + newName = ChooseRelationName(get_rel_name(ind_oid), + NULL, + "repacknew", + get_rel_namespace(ind->rd_index->indrelid), + false); + ind_oid_new = index_create_copy(NewHeap, false, ind_oid, + ind->rd_rel->reltablespace, newName); + result = lappend_oid(result, ind_oid_new); + + index_close(ind, NoLock); + } + + return result; +} + +/* + * Try to start a background worker to perform logical decoding of data + * changes applied to relation while REPACK CONCURRENTLY is copying its + * contents to a new table. + */ +static void +start_repack_decoding_worker(Oid relid) +{ + Size size; + dsm_segment *seg; + DecodingWorkerShared *shared; + shm_mq *mq; + shm_mq_handle *mqh; + BackgroundWorker bgw; + + /* Setup shared memory. */ + size = BUFFERALIGN(offsetof(DecodingWorkerShared, error_queue)) + + BUFFERALIGN(REPACK_ERROR_QUEUE_SIZE); + seg = dsm_create(size, 0); + shared = (DecodingWorkerShared *) dsm_segment_address(seg); + shared->lsn_upto = InvalidXLogRecPtr; + shared->done = false; + SharedFileSetInit(&shared->sfs, seg); + shared->last_exported = -1; + SpinLockInit(&shared->mutex); + shared->dbid = MyDatabaseId; + + /* + * This is the UserId set in cluster_rel(). Security context shouldn't be + * needed for decoding worker. + */ + shared->roleid = GetUserId(); + shared->relid = relid; + ConditionVariableInit(&shared->cv); + shared->backend_proc = MyProc; + shared->backend_pid = MyProcPid; + shared->backend_proc_number = MyProcNumber; + + mq = shm_mq_create((char *) BUFFERALIGN(shared->error_queue), + REPACK_ERROR_QUEUE_SIZE); + shm_mq_set_receiver(mq, MyProc); + mqh = shm_mq_attach(mq, seg, NULL); + + memset(&bgw, 0, sizeof(bgw)); + snprintf(bgw.bgw_name, BGW_MAXLEN, + "REPACK decoding worker for relation \"%s\"", + get_rel_name(relid)); + snprintf(bgw.bgw_type, BGW_MAXLEN, "REPACK decoding worker"); + bgw.bgw_flags = BGWORKER_SHMEM_ACCESS | + BGWORKER_BACKEND_DATABASE_CONNECTION; + bgw.bgw_start_time = BgWorkerStart_RecoveryFinished; + bgw.bgw_restart_time = BGW_NEVER_RESTART; + snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres"); + snprintf(bgw.bgw_function_name, BGW_MAXLEN, "RepackWorkerMain"); + bgw.bgw_main_arg = UInt32GetDatum(dsm_segment_handle(seg)); + bgw.bgw_notify_pid = MyProcPid; + + decoding_worker = palloc0_object(DecodingWorker); + if (!RegisterDynamicBackgroundWorker(&bgw, &decoding_worker->handle)) + ereport(ERROR, + errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED), + errmsg("out of background worker slots"), + errhint("You might need to increase \"%s\".", "max_worker_processes")); + + decoding_worker->seg = seg; + decoding_worker->error_mqh = mqh; + + /* + * The decoding setup must be done before the caller can have XID assigned + * for any reason, otherwise the worker might end up in a deadlock, + * waiting for the caller's transaction to end. Therefore wait here until + * the worker indicates that it has the logical decoding initialized. + */ + ConditionVariablePrepareToSleep(&shared->cv); + for (;;) + { + bool initialized; + + SpinLockAcquire(&shared->mutex); + initialized = shared->initialized; + SpinLockRelease(&shared->mutex); + + if (initialized) + break; + + ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT); + } + ConditionVariableCancelSleep(); +} + +/* + * Stop the decoding worker and cleanup the related resources. + * + * The worker stops on its own when it knows there is no more work to do, but + * we need to stop it explicitly at least on ERROR in the launching backend. + */ +static void +stop_repack_decoding_worker(void) +{ + BgwHandleStatus status; + + /* Haven't reached the worker startup? */ + if (decoding_worker == NULL) + return; + + /* Could not register the worker? */ + if (decoding_worker->handle == NULL) + return; + + TerminateBackgroundWorker(decoding_worker->handle); + /* The worker should really exit before the REPACK command does. */ + HOLD_INTERRUPTS(); + status = WaitForBackgroundWorkerShutdown(decoding_worker->handle); + RESUME_INTERRUPTS(); + + if (status == BGWH_POSTMASTER_DIED) + ereport(FATAL, + errcode(ERRCODE_ADMIN_SHUTDOWN), + errmsg("postmaster exited during REPACK command")); + + shm_mq_detach(decoding_worker->error_mqh); + + /* + * If we could not cancel the current sleep due to ERROR, do that before + * we detach from the shared memory the condition variable is located in. + * If we did not, the bgworker ERROR handling code would try and fail + * badly. + */ + ConditionVariableCancelSleep(); + + dsm_detach(decoding_worker->seg); + pfree(decoding_worker); + decoding_worker = NULL; +} + +/* + * Get the initial snapshot from the decoding worker. + */ +static Snapshot +get_initial_snapshot(DecodingWorker *worker) +{ + DecodingWorkerShared *shared; + char fname[MAXPGPATH]; + BufFile *file; + Size snap_size; + char *snap_space; + Snapshot snapshot; + + shared = (DecodingWorkerShared *) dsm_segment_address(worker->seg); + + /* + * The worker needs to initialize the logical decoding, which usually + * takes some time. Therefore it makes sense to prepare for the sleep + * first. + */ + ConditionVariablePrepareToSleep(&shared->cv); + for (;;) + { + int last_exported; + + SpinLockAcquire(&shared->mutex); + last_exported = shared->last_exported; + SpinLockRelease(&shared->mutex); + + /* + * Has the worker exported the file we are waiting for? + */ + if (last_exported == WORKER_FILE_SNAPSHOT) + break; + + ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT); + } + ConditionVariableCancelSleep(); + + /* Read the snapshot from a file. */ + DecodingWorkerFileName(fname, shared->relid, WORKER_FILE_SNAPSHOT); + file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false); + BufFileReadExact(file, &snap_size, sizeof(snap_size)); + snap_space = (char *) palloc(snap_size); + BufFileReadExact(file, snap_space, snap_size); + BufFileClose(file); + + /* Restore it. */ + snapshot = RestoreSnapshot(snap_space); + pfree(snap_space); + + return snapshot; +} + +/* + * Generate worker's file name into 'fname', which must be of size MAXPGPATH. + * If relations of the same 'relid' happen to be processed at the same time, + * they must be from different databases and therefore different backends must + * be involved. + */ +void +DecodingWorkerFileName(char *fname, Oid relid, uint32 seq) +{ + /* The PID is already present in the fileset name, so we needn't add it */ + snprintf(fname, MAXPGPATH, "%u-%u", relid, seq); +} + +/* + * Handle receipt of an interrupt indicating a repack worker message. + * + * Note: this is called within a signal handler! All we can do is set + * a flag that will cause the next CHECK_FOR_INTERRUPTS() to invoke + * ProcessRepackMessages(). + */ +void +HandleRepackMessageInterrupt(void) +{ + InterruptPending = true; + RepackMessagePending = true; + SetLatch(MyLatch); +} + +/* + * Process any queued protocol messages received from the repack worker. + */ +void +ProcessRepackMessages(void) +{ + MemoryContext oldcontext; + static MemoryContext hpm_context = NULL; + + /* + * Nothing to do if we haven't launched the worker yet or have already + * terminated it. + */ + if (decoding_worker == NULL) + return; + + /* + * This is invoked from ProcessInterrupts(), and since some of the + * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential + * for recursive calls if more signals are received while this runs. It's + * unclear that recursive entry would be safe, and it doesn't seem useful + * even if it is safe, so let's block interrupts until done. + */ + HOLD_INTERRUPTS(); + + /* + * Moreover, CurrentMemoryContext might be pointing almost anywhere. We + * don't want to risk leaking data into long-lived contexts, so let's do + * our work here in a private context that we can reset on each use. + */ + if (hpm_context == NULL) /* first time through? */ + hpm_context = AllocSetContextCreate(TopMemoryContext, + "ProcessRepackMessages", + ALLOCSET_DEFAULT_SIZES); + else + MemoryContextReset(hpm_context); + + oldcontext = MemoryContextSwitchTo(hpm_context); + + /* OK to process messages. Reset the flag saying there are more to do. */ + RepackMessagePending = false; + + /* + * Read as many messages as we can from the worker, but stop when no more + * messages can be read from the worker without blocking. + */ + while (true) + { + shm_mq_result res; + Size nbytes; + void *data; + + res = shm_mq_receive(decoding_worker->error_mqh, &nbytes, + &data, true); + if (res == SHM_MQ_WOULD_BLOCK) + break; + else if (res == SHM_MQ_SUCCESS) + { + StringInfoData msg; + + initStringInfo(&msg); + appendBinaryStringInfo(&msg, data, nbytes); + ProcessRepackMessage(&msg); + pfree(msg.data); + } + else + { + /* + * The decoding worker is special in that it exits as soon as it + * has its work done. Thus the DETACHED result code is fine. + */ + Assert(res == SHM_MQ_DETACHED); + + break; + } + } + + MemoryContextSwitchTo(oldcontext); + + /* Might as well clear the context on our way out */ + MemoryContextReset(hpm_context); + + RESUME_INTERRUPTS(); +} + +/* + * Process a single protocol message received from a single parallel worker. + */ +static void +ProcessRepackMessage(StringInfo msg) +{ + char msgtype; + + msgtype = pq_getmsgbyte(msg); + + switch (msgtype) + { + case PqMsg_ErrorResponse: + case PqMsg_NoticeResponse: + { + ErrorData edata; + + /* Parse ErrorResponse or NoticeResponse. */ + pq_parse_errornotice(msg, &edata); + + /* Death of a worker isn't enough justification for suicide. */ + edata.elevel = Min(edata.elevel, ERROR); + + /* + * Add a context line to show that this is a message propagated + * from the worker. Otherwise, it can sometimes be confusing + * to understand what actually happened. + */ + if (edata.context) + edata.context = psprintf("%s\n%s", edata.context, + _("REPACK decoding worker")); + else + edata.context = pstrdup(_("REPACK decoding worker")); + + /* Rethrow error or print notice. */ + ThrowErrorData(&edata); + + break; + } + + default: + { + elog(ERROR, "unrecognized message type received from decoding worker: %c (message length %d bytes)", + msgtype, msg->len); + } + } +} diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 81a55a33ef2..539969d6eef 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -893,6 +893,7 @@ static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence) { finish_heap_swap(matviewOid, OIDNewHeap, false, false, true, true, + true, /* reindex */ RecentXmin, ReadNextMultiXactId(), relpersistence); } diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build index 90c7e37a429..4a0e6f0002b 100644 --- a/src/backend/commands/meson.build +++ b/src/backend/commands/meson.build @@ -39,6 +39,7 @@ backend_sources += files( 'proclang.c', 'propgraphcmds.c', 'publicationcmds.c', + 'repack_worker.c', 'schemacmds.c', 'seclabel.c', 'sequence.c', diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c new file mode 100644 index 00000000000..35ab1a25c42 --- /dev/null +++ b/src/backend/commands/repack_worker.c @@ -0,0 +1,546 @@ +/*------------------------------------------------------------------------- + * + * repack_worker.c + * Implementation of the background worker for ad-hoc logical decoding + * during REPACK (CONCURRENTLY). + * + * + * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group + * Portions Copyright (c) 1994-5, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/repack_worker.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/table.h" +#include "access/xlog_internal.h" +#include "access/xlogutils.h" +#include "access/xlogwait.h" +#include "commands/cluster.h" +#include "commands/repack_internal.h" +#include "libpq/pqmq.h" +#include "replication/snapbuild.h" +#include "storage/ipc.h" +#include "storage/proc.h" +#include "tcop/tcopprot.h" +#include "utils/memutils.h" + +#define REPL_PLUGIN_NAME "pgoutput_repack" + +static void repack_worker_internal(dsm_segment *seg); +static void RepackWorkerShutdown(int code, Datum arg); +static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid); +static void repack_cleanup_logical_decoding(LogicalDecodingContext *ctx); +static void export_initial_snapshot(Snapshot snapshot, + DecodingWorkerShared *shared); +static bool decode_concurrent_changes(LogicalDecodingContext *ctx, + DecodingWorkerShared *shared); + + +/* Is this process a REPACK worker? */ +static bool is_repack_worker = false; + +/* The WAL segment being decoded. */ +static XLogSegNo repack_current_segment = 0; + +/* + * Keep track of the table we're processing, to skip logical decoding of data + * from other relations. + */ +static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid}; +static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid}; + + +/* REPACK decoding worker entry point */ +void +RepackWorkerMain(Datum main_arg) +{ + dsm_segment *seg; + DecodingWorkerShared *shared; + shm_mq *mq; + shm_mq_handle *mqh; + + is_repack_worker = true; + + /* + * Override the default bgworker_die() with die() so we can use + * CHECK_FOR_INTERRUPTS(). + */ + pqsignal(SIGTERM, die); + BackgroundWorkerUnblockSignals(); + + seg = dsm_attach(DatumGetUInt32(main_arg)); + if (seg == NULL) + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not map dynamic shared memory segment")); + + shared = (DecodingWorkerShared *) dsm_segment_address(seg); + + /* Arrange to signal the leader if we exit. */ + backend_pid = shared->backend_pid; + backend_proc_number = shared->backend_proc_number; + before_shmem_exit(RepackWorkerShutdown, PointerGetDatum(seg)); + + /* + * Join locking group - see the comments around the call of + * start_repack_decoding_worker(). + */ + if (!BecomeLockGroupMember(shared->backend_proc, backend_pid)) + /* The leader is not running anymore. */ + return; + + /* + * Setup a queue to send error messages to the backend that launched this + * worker. + */ + mq = (shm_mq *) (char *) BUFFERALIGN(shared->error_queue); + shm_mq_set_sender(mq, MyProc); + mqh = shm_mq_attach(mq, seg, NULL); + pq_redirect_to_shm_mq(seg, mqh); + pq_set_parallel_leader(shared->backend_pid, + shared->backend_proc_number); + + /* Connect to the database. */ + BackgroundWorkerInitializeConnectionByOid(shared->dbid, shared->roleid, 0); + + repack_worker_internal(seg); +} + +static void +repack_worker_internal(dsm_segment *seg) +{ + DecodingWorkerShared *shared; + LogicalDecodingContext *decoding_ctx; + SharedFileSet *sfs; + Snapshot snapshot; + + /* + * Transaction is needed to open relation, and it also provides us with a + * resource owner. + */ + StartTransactionCommand(); + + shared = (DecodingWorkerShared *) dsm_segment_address(seg); + + /* + * Not sure the spinlock is needed here - the backend should not change + * anything in the shared memory until we have serialized the snapshot. + */ + SpinLockAcquire(&shared->mutex); + Assert(!XLogRecPtrIsValid(shared->lsn_upto)); + sfs = &shared->sfs; + SpinLockRelease(&shared->mutex); + + SharedFileSetAttach(sfs, seg); + + /* + * Prepare to capture the concurrent data changes ourselves. + */ + decoding_ctx = repack_setup_logical_decoding(shared->relid); + + /* Announce that we're ready. */ + SpinLockAcquire(&shared->mutex); + shared->initialized = true; + SpinLockRelease(&shared->mutex); + ConditionVariableSignal(&shared->cv); + + /* Build the initial snapshot and export it. */ + snapshot = SnapBuildInitialSnapshot(decoding_ctx->snapshot_builder, true); + export_initial_snapshot(snapshot, shared); + + /* + * Only historic snapshots should be used now. Do not let us restrict the + * progress of xmin horizon. + */ + InvalidateCatalogSnapshot(); + + for (;;) + { + bool stop = decode_concurrent_changes(decoding_ctx, shared); + + if (stop) + break; + + } + + /* Cleanup. */ + repack_cleanup_logical_decoding(decoding_ctx); + CommitTransactionCommand(); +} + +/* + * See ParallelWorkerShutdown for details. + */ +static void +RepackWorkerShutdown(int code, Datum arg) +{ + SendProcSignal(backend_pid, + PROCSIG_REPACK_MESSAGE, + backend_proc_number); + + dsm_detach((dsm_segment *) DatumGetPointer(arg)); +} + +bool +IsRepackWorker(void) +{ + return is_repack_worker; +} + +/* + * This function is much like pg_create_logical_replication_slot() except that + * the new slot is neither released (if anyone else could read changes from + * our slot, we could miss changes other backends do while we copy the + * existing data into temporary table), nor persisted (it's easier to handle + * crash by restarting all the work from scratch). + */ +static LogicalDecodingContext * +repack_setup_logical_decoding(Oid relid) +{ + Relation rel; + Oid toastrelid; + LogicalDecodingContext *ctx; + NameData slotname; + RepackDecodingState *dstate; + MemoryContext oldcxt; + + /* + * REPACK CONCURRENTLY is not allowed in a transaction block, so this + * should never fire. + */ + Assert(!TransactionIdIsValid(GetTopTransactionIdIfAny())); + + /* + * Make sure we can use logical decoding. + */ + CheckSlotPermissions(); + CheckLogicalDecodingRequirements(); + + /* + * A single backend should not execute multiple REPACK commands at a time, + * so use PID to make the slot unique. + * + * RS_TEMPORARY so that the slot gets cleaned up on ERROR. + */ + snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid); + ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false, + false); + + EnsureLogicalDecodingEnabled(); + + /* + * Neither prepare_write nor do_write callback nor update_progress is + * useful for us. + */ + ctx = CreateInitDecodingContext(REPL_PLUGIN_NAME, + NIL, + true, + InvalidXLogRecPtr, + XL_ROUTINE(.page_read = read_local_xlog_page, + .segment_open = wal_segment_open, + .segment_close = wal_segment_close), + NULL, NULL, NULL); + + /* + * We don't have control on setting fast_forward, so at least check it. + */ + Assert(!ctx->fast_forward); + + /* Avoid logical decoding of other relations. */ + rel = table_open(relid, AccessShareLock); + repacked_rel_locator = rel->rd_locator; + toastrelid = rel->rd_rel->reltoastrelid; + if (OidIsValid(toastrelid)) + { + Relation toastrel; + + /* Avoid logical decoding of other TOAST relations. */ + toastrel = table_open(toastrelid, AccessShareLock); + repacked_rel_toast_locator = toastrel->rd_locator; + table_close(toastrel, AccessShareLock); + } + table_close(rel, AccessShareLock); + + DecodingContextFindStartpoint(ctx); + + /* + * decode_concurrent_changes() needs non-blocking callback. + */ + ctx->reader->routine.page_read = read_local_xlog_page_no_wait; + + /* Some WAL records should have been read. */ + Assert(ctx->reader->EndRecPtr != InvalidXLogRecPtr); + + /* + * Initialize repack_current_segment so that we can notice WAL segment + * boundaries. + */ + XLByteToSeg(ctx->reader->EndRecPtr, repack_current_segment, + wal_segment_size); + + /* Our private state belongs to the decoding context. */ + oldcxt = MemoryContextSwitchTo(ctx->context); + + /* + * read_local_xlog_page_no_wait() needs to be able to indicate the end of + * WAL. + */ + ctx->reader->private_data = palloc0_object(ReadLocalXLogPageNoWaitPrivate); + dstate = palloc0_object(RepackDecodingState); + MemoryContextSwitchTo(oldcxt); + +#ifdef USE_ASSERT_CHECKING + dstate->relid = relid; +#endif + + dstate->change_cxt = AllocSetContextCreate(ctx->context, + "REPACK - change", + ALLOCSET_DEFAULT_SIZES); + + /* The file will be set as soon as we have it opened. */ + dstate->file = NULL; + + /* + * Memory context and resource owner for long-lived resources. + */ + dstate->worker_cxt = CurrentMemoryContext; + dstate->worker_resowner = CurrentResourceOwner; + + ctx->output_writer_private = dstate; + + return ctx; +} + +static void +repack_cleanup_logical_decoding(LogicalDecodingContext *ctx) +{ + RepackDecodingState *dstate; + + dstate = (RepackDecodingState *) ctx->output_writer_private; + if (dstate->slot) + ExecDropSingleTupleTableSlot(dstate->slot); + + FreeDecodingContext(ctx); + ReplicationSlotDropAcquired(); +} + +/* + * Make snapshot available to the backend that launched the decoding worker. + */ +static void +export_initial_snapshot(Snapshot snapshot, DecodingWorkerShared *shared) +{ + char fname[MAXPGPATH]; + BufFile *file; + Size snap_size; + char *snap_space; + + snap_size = EstimateSnapshotSpace(snapshot); + snap_space = (char *) palloc(snap_size); + SerializeSnapshot(snapshot, snap_space); + FreeSnapshot(snapshot); + + DecodingWorkerFileName(fname, shared->relid, shared->last_exported + 1); + file = BufFileCreateFileSet(&shared->sfs.fs, fname); + /* To make restoration easier, write the snapshot size first. */ + BufFileWrite(file, &snap_size, sizeof(snap_size)); + BufFileWrite(file, snap_space, snap_size); + pfree(snap_space); + BufFileClose(file); + + /* Increase the counter to tell the backend that the file is available. */ + SpinLockAcquire(&shared->mutex); + shared->last_exported++; + SpinLockRelease(&shared->mutex); + ConditionVariableSignal(&shared->cv); +} + +/* + * Decode logical changes from the WAL sequence and store them to a file. + * + * If true is returned, there is no more work for the worker. + */ +static bool +decode_concurrent_changes(LogicalDecodingContext *ctx, + DecodingWorkerShared *shared) +{ + RepackDecodingState *dstate; + XLogRecPtr lsn_upto; + bool done; + char fname[MAXPGPATH]; + + dstate = (RepackDecodingState *) ctx->output_writer_private; + + /* Open the output file. */ + DecodingWorkerFileName(fname, shared->relid, shared->last_exported + 1); + dstate->file = BufFileCreateFileSet(&shared->sfs.fs, fname); + + SpinLockAcquire(&shared->mutex); + lsn_upto = shared->lsn_upto; + done = shared->done; + SpinLockRelease(&shared->mutex); + + while (true) + { + XLogRecord *record; + XLogSegNo segno_new; + char *errm = NULL; + XLogRecPtr end_lsn; + + CHECK_FOR_INTERRUPTS(); + + record = XLogReadRecord(ctx->reader, &errm); + if (record) + { + LogicalDecodingProcessRecord(ctx, ctx->reader); + + /* + * If WAL segment boundary has been crossed, inform the decoding + * system that the catalog_xmin can advance. + * + * TODO Does it make sense to confirm more often? Segment size + * seems appropriate for restart_lsn (because less than a segment + * cannot be recycled anyway), however more frequent checks might + * be beneficial for catalog_xmin. + */ + end_lsn = ctx->reader->EndRecPtr; + XLByteToSeg(end_lsn, segno_new, wal_segment_size); + if (segno_new != repack_current_segment) + { + LogicalConfirmReceivedLocation(end_lsn); + elog(DEBUG1, "REPACK: confirmed receive location %X/%X", + (uint32) (end_lsn >> 32), (uint32) end_lsn); + repack_current_segment = segno_new; + } + } + else + { + ReadLocalXLogPageNoWaitPrivate *priv; + + if (errm) + ereport(ERROR, + errmsg("%s", errm)); + + /* + * In the decoding loop we do not want to get blocked when there + * is no more WAL available, otherwise the loop would become + * uninterruptible. + */ + priv = (ReadLocalXLogPageNoWaitPrivate *) ctx->reader->private_data; + if (priv->end_of_wal) + /* Do not miss the end of WAL condition next time. */ + priv->end_of_wal = false; + else + ereport(ERROR, + errmsg("could not read WAL record")); + } + + /* + * Whether we could read new record or not, keep checking if + * 'lsn_upto' was specified. + */ + if (!XLogRecPtrIsValid(lsn_upto)) + { + SpinLockAcquire(&shared->mutex); + lsn_upto = shared->lsn_upto; + /* 'done' should be set at the same time as 'lsn_upto' */ + done = shared->done; + SpinLockRelease(&shared->mutex); + } + if (XLogRecPtrIsValid(lsn_upto) && + ctx->reader->EndRecPtr >= lsn_upto) + break; + + if (record == NULL) + { + int64 timeout = 0; + WaitLSNResult res; + + /* + * Before we retry reading, wait until new WAL is flushed. + * + * There is a race condition such that the backend executing + * REPACK determines 'lsn_upto', but before it sets the shared + * variable, we reach the end of WAL. In that case we'd need to + * wait until the next WAL flush (unrelated to REPACK). Although + * that should not be a problem in a busy system, it might be + * noticeable in other cases, including regression tests (which + * are not necessarily executed in parallel). Therefore it makes + * sense to use timeout. + * + * If lsn_upto is valid, WAL records having LSN lower than that + * should already have been flushed to disk. + */ + if (!XLogRecPtrIsValid(lsn_upto)) + timeout = 100L; + res = WaitForLSN(WAIT_LSN_TYPE_PRIMARY_FLUSH, + ctx->reader->EndRecPtr + 1, + timeout); + if (res != WAIT_LSN_RESULT_SUCCESS && + res != WAIT_LSN_RESULT_TIMEOUT) + ereport(ERROR, + errmsg("waiting for WAL failed")); + } + } + + /* + * Close the file so we can make it available to the backend. + */ + BufFileClose(dstate->file); + dstate->file = NULL; + SpinLockAcquire(&shared->mutex); + shared->lsn_upto = InvalidXLogRecPtr; + shared->last_exported++; + SpinLockRelease(&shared->mutex); + ConditionVariableSignal(&shared->cv); + + return done; +} + +/* + * Does the WAL record contain a data change that this backend does not need + * to decode on behalf of REPACK (CONCURRENTLY)? + */ +bool +change_useless_for_repack(XLogRecordBuffer *buf) +{ + XLogReaderState *r = buf->record; + RelFileLocator locator; + + /* TOAST locator should not be set unless the main is. */ + Assert(!OidIsValid(repacked_rel_toast_locator.relNumber) || + OidIsValid(repacked_rel_locator.relNumber)); + + /* + * Backends not involved in REPACK (CONCURRENTLY) should not do the + * filtering. + */ + if (!OidIsValid(repacked_rel_locator.relNumber)) + return false; + + /* + * If the record does not contain the block 0, it's probably not INSERT / + * UPDATE / DELETE. In any case, we do not have enough information to + * filter the change out. + */ + if (!XLogRecGetBlockTagExtended(r, 0, &locator, NULL, NULL, NULL)) + return false; + + /* + * Decode the change if it belongs to the table we are repacking, or if it + * belongs to its TOAST relation. + */ + if (RelFileLocatorEquals(locator, repacked_rel_locator)) + return false; + if (OidIsValid(repacked_rel_toast_locator.relNumber) && + RelFileLocatorEquals(locator, repacked_rel_toast_locator)) + return false; + + /* Filter out changes of other tables. */ + return true; +} diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c69c12dc014..cc70439627b 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -6058,6 +6058,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, finish_heap_swap(tab->relid, OIDNewHeap, false, false, true, !OidIsValid(tab->newTableSpace), + true, /* reindex */ RecentXmin, ReadNextMultiXactId(), persistence); diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..201835f30a4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -127,7 +127,7 @@ static void vac_truncate_clog(TransactionId frozenXID, TransactionId lastSaneFrozenXid, MultiXactId lastSaneMinMulti); static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, - BufferAccessStrategy bstrategy); + BufferAccessStrategy bstrategy, bool isTopLevel); static double compute_parallel_delay(void); static VacOptValue get_vacoptval_from_boolean(DefElem *def); static bool vac_tid_reaped(ItemPointer itemptr, void *state); @@ -630,7 +630,8 @@ vacuum(List *relations, const VacuumParams params, BufferAccessStrategy bstrateg if (params.options & VACOPT_VACUUM) { - if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy)) + if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy, + isTopLevel)) continue; } @@ -2004,7 +2005,7 @@ vac_truncate_clog(TransactionId frozenXID, */ static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, - BufferAccessStrategy bstrategy) + BufferAccessStrategy bstrategy, bool isTopLevel) { LOCKMODE lmode; Relation rel; @@ -2295,7 +2296,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, /* VACUUM FULL is a variant of REPACK; see cluster.c */ cluster_rel(REPACK_COMMAND_VACUUMFULL, rel, InvalidOid, - &cluster_params); + &cluster_params, isTopLevel); /* cluster_rel closes the relation, but keeps lock */ rel = NULL; @@ -2338,7 +2339,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, toast_vacuum_params.options |= VACOPT_PROCESS_MAIN; toast_vacuum_params.toast_parent = relid; - vacuum_rel(toast_relid, NULL, toast_vacuum_params, bstrategy); + vacuum_rel(toast_relid, NULL, toast_vacuum_params, bstrategy, + isTopLevel); } /* diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 4cd5e262e0f..680c29f35d5 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1522,14 +1522,17 @@ ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, bool changingPart) { EState *estate = context->estate; + int options = TABLE_DELETE_WAIT; + + if (changingPart) + options |= TABLE_DELETE_CHANGING_PART; return table_tuple_delete(resultRelInfo->ri_RelationDesc, tupleid, estate->es_output_cid, estate->es_snapshot, estate->es_crosscheck_snapshot, - true /* wait for commit */ , - &context->tmfd, - changingPart); + options, + &context->tmfd); } /* @@ -2333,7 +2336,7 @@ lreplace: estate->es_output_cid, estate->es_snapshot, estate->es_crosscheck_snapshot, - true /* wait for commit */ , + TABLE_UPDATE_WAIT, &context->tmfd, &updateCxt->lockmode, &updateCxt->updateIndexes); diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c index 22e5164adbf..1000b7bb06e 100644 --- a/src/backend/libpq/pqmq.c +++ b/src/backend/libpq/pqmq.c @@ -14,6 +14,7 @@ #include "postgres.h" #include "access/parallel.h" +#include "commands/cluster.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" #include "libpq/pqmq.h" @@ -177,6 +178,10 @@ mq_putmessage(char msgtype, const char *s, size_t len) SendProcSignal(pq_mq_parallel_leader_pid, PROCSIG_PARALLEL_APPLY_MESSAGE, pq_mq_parallel_leader_proc_number); + else if (IsRepackWorker()) + SendProcSignal(pq_mq_parallel_leader_pid, + PROCSIG_REPACK_MESSAGE, + pq_mq_parallel_leader_proc_number); else { Assert(IsParallelWorker()); diff --git a/src/backend/meson.build b/src/backend/meson.build index 4f5292d8f88..5e3cfe2d6f8 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -219,5 +219,6 @@ pg_test_mod_args = pg_mod_args + { subdir('jit/llvm') subdir('replication/libpqwalreceiver') subdir('replication/pgoutput') +subdir('replication/pgoutput_repack') subdir('snowball') subdir('utils/mb/conversion_procs') diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c index 4d8b46adb46..cb9c60dfe31 100644 --- a/src/backend/postmaster/bgworker.c +++ b/src/backend/postmaster/bgworker.c @@ -13,6 +13,7 @@ #include "postgres.h" #include "access/parallel.h" +#include "commands/cluster.h" #include "libpq/pqsignal.h" #include "miscadmin.h" #include "pgstat.h" @@ -136,6 +137,10 @@ static const struct .fn_name = "ParallelWorkerMain", .fn_addr = ParallelWorkerMain }, + { + .fn_name = "RepackWorkerMain", + .fn_addr = RepackWorkerMain + }, { .fn_name = "SequenceSyncWorkerMain", .fn_addr = SequenceSyncWorkerMain @@ -144,7 +149,6 @@ static const struct .fn_name = "TableSyncWorkerMain", .fn_addr = TableSyncWorkerMain }, - }; /* Private functions. */ diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index 3c027bcb2f7..f172c0b2b40 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -33,6 +33,7 @@ #include "access/xlogreader.h" #include "access/xlogrecord.h" #include "catalog/pg_control.h" +#include "commands/cluster.h" #include "replication/decode.h" #include "replication/logical.h" #include "replication/message.h" @@ -420,7 +421,8 @@ heap2_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) { case XLOG_HEAP2_MULTI_INSERT: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeMultiInsert(ctx, buf); break; case XLOG_HEAP2_NEW_CID: @@ -465,6 +467,15 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) TransactionId xid = XLogRecGetXid(buf->record); SnapBuild *builder = ctx->snapshot_builder; + /* + * XXX Should we return here if change_useless_for_repack() returns true, + * instead of calling the function below? Unlike the fast-forward case, we + * shouldn't need the base snapshot for the containing transaction until + * we receive a change that belongs to the table being REPACKed. Thus it + * should be fine to skip SnapBuildProcessChange(), and therefore + * reorderbuffer.c can create the transaction later. + */ + ReorderBufferProcessXid(ctx->reorder, xid, buf->origptr); /* @@ -482,7 +493,8 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) { case XLOG_HEAP_INSERT: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeInsert(ctx, buf); break; @@ -494,19 +506,22 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_HEAP_HOT_UPDATE: case XLOG_HEAP_UPDATE: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeUpdate(ctx, buf); break; case XLOG_HEAP_DELETE: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeDelete(ctx, buf); break; case XLOG_HEAP_TRUNCATE: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeTruncate(ctx, buf); break; @@ -522,7 +537,8 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_HEAP_CONFIRM: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeSpecConfirm(ctx, buf); break; @@ -1019,6 +1035,15 @@ DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) xlrec = (xl_heap_delete *) XLogRecGetData(r); + /* + * Ignore changes which are considered useless for logical decoding. + * Currently such changes are created by REPACK (CONCURRENTLY) when + * replays DELETE commands on the new table (which is not yet visible to + * other transactions). + */ + if (xlrec->flags & XLH_DELETE_NO_LOGICAL) + return; + /* only interested in our database */ XLogRecGetBlockTag(r, 0, &target_locator, NULL, NULL); if (target_locator.dbOid != ctx->slot->data.database) diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index 603a2b94d05..7651b187418 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -194,7 +194,11 @@ StartupDecodingContext(List *output_plugin_options, ctx->slot = slot; - ctx->reader = XLogReaderAllocate(wal_segment_size, NULL, xl_routine, ctx); + /* + * TODO A separate patch for PG core, unless there's really a reason to + * pass ctx for private_data (May extensions expect ctx?). + */ + ctx->reader = XLogReaderAllocate(wal_segment_size, NULL, xl_routine, NULL); if (!ctx->reader) ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index 37f0c6028bd..9cf499ce7c6 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -440,7 +440,7 @@ SnapBuildBuildSnapshot(SnapBuild *builder) * for loading in different transaction. */ Snapshot -SnapBuildInitialSnapshot(SnapBuild *builder) +SnapBuildInitialSnapshot(SnapBuild *builder, bool repack) { Snapshot snap; TransactionId xid; @@ -448,7 +448,7 @@ SnapBuildInitialSnapshot(SnapBuild *builder) TransactionId *newxip; int newxcnt = 0; - Assert(XactIsoLevel == XACT_REPEATABLE_READ); + Assert(XactIsoLevel == XACT_REPEATABLE_READ || repack); Assert(builder->building_full_snapshot); /* don't allow older snapshots */ @@ -526,6 +526,11 @@ SnapBuildInitialSnapshot(SnapBuild *builder) snap->xcnt = newxcnt; snap->xip = newxip; + /* + * FreeSnapshot() is more appropriate for REPACK than counting references. + */ + snap->copied = repack; + return snap; } @@ -558,7 +563,7 @@ SnapBuildExportSnapshot(SnapBuild *builder) XactIsoLevel = XACT_REPEATABLE_READ; XactReadOnly = true; - snap = SnapBuildInitialSnapshot(builder); + snap = SnapBuildInitialSnapshot(builder, false); /* * now that we've built a plain snapshot, make it active and use the diff --git a/src/backend/replication/pgoutput_repack/Makefile b/src/backend/replication/pgoutput_repack/Makefile new file mode 100644 index 00000000000..4efeb713b70 --- /dev/null +++ b/src/backend/replication/pgoutput_repack/Makefile @@ -0,0 +1,32 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for src/backend/replication/pgoutput_repack +# +# IDENTIFICATION +# src/backend/replication/pgoutput_repack +# +#------------------------------------------------------------------------- + +subdir = src/backend/replication/pgoutput_repack +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = \ + $(WIN32RES) \ + pgoutput_repack.o +PGFILEDESC = "pgoutput_repack - logical replication output plugin for REPACK command" +NAME = pgoutput_repack + +all: all-shared-lib + +include $(top_srcdir)/src/Makefile.shlib + +install: all installdirs install-lib + +installdirs: installdirs-lib + +uninstall: uninstall-lib + +clean distclean: clean-lib + rm -f $(OBJS) diff --git a/src/backend/replication/pgoutput_repack/meson.build b/src/backend/replication/pgoutput_repack/meson.build new file mode 100644 index 00000000000..6a88c0fb08d --- /dev/null +++ b/src/backend/replication/pgoutput_repack/meson.build @@ -0,0 +1,18 @@ +# Copyright (c) 2022-2026, PostgreSQL Global Development Group + +pgoutput_repack_sources = files( + 'pgoutput_repack.c', +) + +if host_system == 'windows' + pgoutput_repack_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pgoutput_repack', + '--FILEDESC', 'pgoutput_repack - logical replication output plugin for REPACK command',]) +endif + +pgoutput_repack = shared_module('pgoutput_repack', + pgoutput_repack_sources, + kwargs: pg_mod_args, +) + +backend_targets += pgoutput_repack diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c new file mode 100644 index 00000000000..221260ca0a8 --- /dev/null +++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c @@ -0,0 +1,290 @@ +/*------------------------------------------------------------------------- + * + * pgoutput_repack.c + * Logical Replication output plugin for REPACK command + * + * Copyright (c) 2012-2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/replication/pgoutput_repack/pgoutput_repack.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/detoast.h" +#include "commands/repack_internal.h" +#include "replication/snapbuild.h" +#include "utils/memutils.h" + +PG_MODULE_MAGIC; + +static void repack_startup(LogicalDecodingContext *ctx, + OutputPluginOptions *opt, bool is_init); +static void repack_shutdown(LogicalDecodingContext *ctx); +static void repack_begin_txn(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn); +static void repack_commit_txn(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, XLogRecPtr commit_lsn); +static void repack_process_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + Relation rel, ReorderBufferChange *change); +static void repack_store_change(LogicalDecodingContext *ctx, Relation relation, + ConcurrentChangeKind kind, HeapTuple tuple); + +void +_PG_output_plugin_init(OutputPluginCallbacks *cb) +{ + cb->startup_cb = repack_startup; + cb->begin_cb = repack_begin_txn; + cb->change_cb = repack_process_change; + cb->commit_cb = repack_commit_txn; + cb->shutdown_cb = repack_shutdown; +} + + +/* initialize this plugin */ +static void +repack_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, + bool is_init) +{ + ctx->output_plugin_private = NULL; + + /* Probably unnecessary, as we don't use the SQL interface ... */ + opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT; + + if (ctx->output_plugin_options != NIL) + { + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("this plugin does not expect any options")); + } +} + +static void +repack_shutdown(LogicalDecodingContext *ctx) +{ +} + +/* + * As we don't release the slot during processing of particular table, there's + * no room for SQL interface, even for debugging purposes. Therefore we need + * neither OutputPluginPrepareWrite() nor OutputPluginWrite() in the plugin + * callbacks. (Although we might want to write custom callbacks, this API + * seems to be unnecessarily generic for our purposes.) + */ + +/* BEGIN callback */ +static void +repack_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn) +{ +} + +/* COMMIT callback */ +static void +repack_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + XLogRecPtr commit_lsn) +{ +} + +/* + * Callback for individual changed tuples + */ +static void +repack_process_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + Relation relation, ReorderBufferChange *change) +{ + RepackDecodingState *private PG_USED_FOR_ASSERTS_ONLY = + (RepackDecodingState *) ctx->output_writer_private; + + /* Changes of other relation should not have been decoded. */ + Assert(RelationGetRelid(relation) == private->relid); + + /* Decode entry depending on its type */ + switch (change->action) + { + case REORDER_BUFFER_CHANGE_INSERT: + { + HeapTuple newtuple; + + newtuple = change->data.tp.newtuple; + + /* + * Identity checks in the main function should have made this + * impossible. + */ + if (newtuple == NULL) + elog(ERROR, "incomplete insert info"); + + repack_store_change(ctx, relation, CHANGE_INSERT, newtuple); + } + break; + case REORDER_BUFFER_CHANGE_UPDATE: + { + HeapTuple oldtuple, + newtuple; + + oldtuple = change->data.tp.oldtuple; + newtuple = change->data.tp.newtuple; + + if (newtuple == NULL) + elog(ERROR, "incomplete update info"); + + if (oldtuple != NULL) + repack_store_change(ctx, relation, CHANGE_UPDATE_OLD, oldtuple); + + repack_store_change(ctx, relation, CHANGE_UPDATE_NEW, newtuple); + } + break; + case REORDER_BUFFER_CHANGE_DELETE: + { + HeapTuple oldtuple; + + oldtuple = change->data.tp.oldtuple; + + if (oldtuple == NULL) + elog(ERROR, "incomplete delete info"); + + repack_store_change(ctx, relation, CHANGE_DELETE, oldtuple); + } + break; + default: + + /* + * Should not come here. This includes TRUNCATE of the table being + * processed. heap_decode() cannot check the file locator easily, + * but we assume that TRUNCATE uses AccessExclusiveLock on the + * table so it should not occur during REPACK (CONCURRENTLY). + */ + Assert(false); + break; + } +} + +/* + * Write the given tuple, with the given change kind, to the repack spill + * file. Later, the repack decoding worker can read these and replay + * the operations on the new copy of the table. + * + * For each change affecting the table being repacked, we store enough + * information about each tuple in it, so that it can be replayed in the + * new copy of the table. + * + * XXX for DELETE and the UPDATE OLD tuples, we could store just the + * replication identity instead of the full tuple. + */ +static void +repack_store_change(LogicalDecodingContext *ctx, Relation relation, + ConcurrentChangeKind kind, HeapTuple tuple) +{ + RepackDecodingState *dstate; + MemoryContext oldcxt; + BufFile *file; + List *attrs_ext = NIL; + int natt_ext; + + dstate = (RepackDecodingState *) ctx->output_writer_private; + file = dstate->file; + + /* Store the change kind. */ + BufFileWrite(file, &kind, 1); + + /* Use a frequently-reset context to avoid dealing with leaks manually */ + oldcxt = MemoryContextSwitchTo(dstate->change_cxt); + + /* + * If the tuple contains "external indirect" attributes, we need to write + * the contents to the file because we have no control over that memory. + */ + if (HeapTupleHasExternal(tuple)) + { + TupleDesc desc = RelationGetDescr(relation); + TupleTableSlot *slot; + + /* Initialize the slot, if not done already */ + if (dstate->slot == NULL) + { + ResourceOwner saveResourceOwner; + + MemoryContextSwitchTo(dstate->worker_cxt); + saveResourceOwner = CurrentResourceOwner; + CurrentResourceOwner = dstate->worker_resowner; + dstate->slot = MakeSingleTupleTableSlot(desc, &TTSOpsHeapTuple); + MemoryContextSwitchTo(dstate->change_cxt); + CurrentResourceOwner = saveResourceOwner; + } + + slot = dstate->slot; + ExecStoreHeapTuple(tuple, slot, false); + + /* + * Loop over all attributes, and find out which ones we need to spill + * separately, to wit: each one that's a non-null varlena and stored + * out of line. + */ + for (int i = 0; i < desc->natts; i++) + { + CompactAttribute *attr = TupleDescCompactAttr(desc, i); + varlena *varlen; + + if (attr->attisdropped || attr->attlen != -1 || + slot_attisnull(slot, i + 1)) + continue; + + slot_getsomeattrs(slot, i + 1); + + /* + * This is a non-null varlena datum, but we only care if it's + * out-of-line + */ + varlen = (varlena *) DatumGetPointer(slot->tts_values[i]); + if (!VARATT_IS_EXTERNAL(varlen)) + continue; + + /* + * We spill any indirect-external attributes separately from the + * heap tuple. Anything else is written as is. + */ + if (VARATT_IS_EXTERNAL_INDIRECT(varlen)) + attrs_ext = lappend(attrs_ext, varlen); + else + { + /* + * Logical decoding should not produce "external expanded" + * attributes (those actually should never appear on disk), so + * only TOASTed attribute can be seen here. + * + * We get here if the table has external values but only + * in-line values are being updated now. + */ + Assert(VARATT_IS_EXTERNAL_ONDISK(varlen)); + } + } + + ExecClearTuple(slot); + } + + /* + * First, write the original heap tuple, prefixed by its length. Note + * that the external-toast tag for each toasted attribute will be present + * in what we write, so that we know where to restore each one later. + */ + BufFileWrite(file, &tuple->t_len, sizeof(tuple->t_len)); + BufFileWrite(file, tuple->t_data, tuple->t_len); + + /* Then, write the number of external attributes we found. */ + natt_ext = list_length(attrs_ext); + BufFileWrite(file, &natt_ext, sizeof(natt_ext)); + + /* Finally, the attributes themselves, if any */ + foreach_ptr(varlena, attr_val, attrs_ext) + { + attr_val = detoast_external_attr(attr_val); + BufFileWrite(file, attr_val, VARSIZE_ANY(attr_val)); + /* These attributes could be large, so free them right away */ + pfree(attr_val); + } + + /* Cleanup. */ + MemoryContextSwitchTo(oldcxt); + MemoryContextReset(dstate->change_cxt); +} diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 66507e9c2dd..fbbe09135bf 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1342,7 +1342,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) { Snapshot snap; - snap = SnapBuildInitialSnapshot(ctx->snapshot_builder); + snap = SnapBuildInitialSnapshot(ctx->snapshot_builder, false); RestoreTransactionSnapshot(snap, MyProc); } diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c index 7e017c8d53b..dd980145ced 100644 --- a/src/backend/storage/ipc/procsignal.c +++ b/src/backend/storage/ipc/procsignal.c @@ -19,6 +19,7 @@ #include "access/parallel.h" #include "commands/async.h" +#include "commands/cluster.h" #include "miscadmin.h" #include "pgstat.h" #include "port/pg_bitutils.h" @@ -700,6 +701,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS) if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE)) HandleParallelApplyMessageInterrupt(); + if (CheckProcSignal(PROCSIG_REPACK_MESSAGE)) + HandleRepackMessageInterrupt(); + if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT)) HandleRecoveryConflictInterrupt(); diff --git a/src/backend/storage/lmgr/generate-lwlocknames.pl b/src/backend/storage/lmgr/generate-lwlocknames.pl index b49007167b0..2e7f1054e62 100644 --- a/src/backend/storage/lmgr/generate-lwlocknames.pl +++ b/src/backend/storage/lmgr/generate-lwlocknames.pl @@ -162,7 +162,7 @@ while (<$lwlocklist>) die "$wait_event_lwlocks[$lwlock_count] defined in wait_event_names.txt but " - . " missing from lwlocklist.h" + . "missing from lwlocklist.h" if $lwlock_count < scalar @wait_event_lwlocks; die diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index b3563113219..4d253eddfa0 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -36,6 +36,7 @@ #include "access/xact.h" #include "catalog/pg_type.h" #include "commands/async.h" +#include "commands/cluster.h" #include "commands/event_trigger.h" #include "commands/explain_state.h" #include "commands/prepare.h" @@ -3576,6 +3577,9 @@ ProcessInterrupts(void) if (ParallelApplyMessagePending) ProcessParallelApplyMessages(); + + if (RepackMessagePending) + ProcessRepackMessages(); } /* diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt index 6be80d2daad..e2f21349997 100644 --- a/src/backend/utils/activity/wait_event_names.txt +++ b/src/backend/utils/activity/wait_event_names.txt @@ -154,6 +154,7 @@ RECOVERY_CONFLICT_SNAPSHOT "Waiting for recovery conflict resolution for a vacuu RECOVERY_CONFLICT_TABLESPACE "Waiting for recovery conflict resolution for dropping a tablespace." RECOVERY_END_COMMAND "Waiting for <xref linkend="guc-recovery-end-command"/> to complete." RECOVERY_PAUSE "Waiting for recovery to be resumed." +REPACK_WORKER_EXPORT "Waiting for decoding worker to export a new output file." REPLICATION_ORIGIN_DROP "Waiting for a replication origin to become inactive so it can be dropped." REPLICATION_SLOT_DROP "Waiting for a replication slot to become inactive so it can be dropped." RESTORE_COMMAND "Waiting for <xref linkend="guc-restore-command"/> to complete." diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c index 2e6197f5f35..e0129df3e40 100644 --- a/src/backend/utils/time/snapmgr.c +++ b/src/backend/utils/time/snapmgr.c @@ -215,7 +215,6 @@ static List *exportedSnapshots = NIL; /* Prototypes for local functions */ static Snapshot CopySnapshot(Snapshot snapshot); static void UnregisterSnapshotNoOwner(Snapshot snapshot); -static void FreeSnapshot(Snapshot snapshot); static void SnapshotResetXmin(void); /* ResourceOwner callbacks to track snapshot references */ @@ -660,7 +659,7 @@ CopySnapshot(Snapshot snapshot) * FreeSnapshot * Free the memory associated with a snapshot. */ -static void +void FreeSnapshot(Snapshot snapshot) { Assert(snapshot->regd_count == 0); diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 523d3f39fc5..e41c965a1cc 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -5227,8 +5227,8 @@ match_previous_words(int pattern_id, * one word, so the above test is correct. */ if (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')) - COMPLETE_WITH("ANALYZE", "VERBOSE"); - else if (TailMatches("ANALYZE", "VERBOSE")) + COMPLETE_WITH("ANALYZE", "CONCURRENTLY", "VERBOSE"); + else if (TailMatches("ANALYZE", "CONCURRENTLY", "VERBOSE")) COMPLETE_WITH("ON", "OFF"); } diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 9b403203006..67d113de10b 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -384,13 +384,13 @@ extern void heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, CommandId cid, int options, BulkInsertState bistate); extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid, - CommandId cid, Snapshot crosscheck, bool wait, - TM_FailureData *tmfd, bool changingPart); + CommandId cid, Snapshot crosscheck, int options, + TM_FailureData *tmfd); extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid); extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid); extern TM_Result heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, - CommandId cid, Snapshot crosscheck, bool wait, + CommandId cid, Snapshot crosscheck, int options, TM_FailureData *tmfd, LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes); extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple, diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h index 516806fcca2..fdca7d821c8 100644 --- a/src/include/access/heapam_xlog.h +++ b/src/include/access/heapam_xlog.h @@ -104,6 +104,8 @@ #define XLH_DELETE_CONTAINS_OLD_KEY (1<<2) #define XLH_DELETE_IS_SUPER (1<<3) #define XLH_DELETE_IS_PARTITION_MOVE (1<<4) +/* See heap_delete() */ +#define XLH_DELETE_NO_LOGICAL (1<<5) /* convenience macro for checking whether any form of old tuple was logged */ #define XLH_DELETE_CONTAINS_OLD \ diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 06084752245..1e51e22344f 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -260,6 +260,15 @@ typedef struct TM_IndexDeleteOp #define TABLE_INSERT_FROZEN 0x0004 #define TABLE_INSERT_NO_LOGICAL 0x0008 +/* "options" flag bits for table_tuple_update */ +#define TABLE_UPDATE_WAIT 0x0001 +#define TABLE_UPDATE_NO_LOGICAL 0x0002 + +/* "options" flag bits for table_tuple_delete */ +#define TABLE_DELETE_WAIT 0x0001 +#define TABLE_DELETE_CHANGING_PART 0x0002 +#define TABLE_DELETE_NO_LOGICAL 0x0004 + /* flag bits for table_tuple_lock */ /* Follow tuples whose update is in progress if lock modes don't conflict */ #define TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS (1 << 0) @@ -535,9 +544,8 @@ typedef struct TableAmRoutine CommandId cid, Snapshot snapshot, Snapshot crosscheck, - bool wait, - TM_FailureData *tmfd, - bool changingPart); + int options, + TM_FailureData *tmfd); /* see table_tuple_update() for reference about parameters */ TM_Result (*tuple_update) (Relation rel, @@ -546,7 +554,7 @@ typedef struct TableAmRoutine CommandId cid, Snapshot snapshot, Snapshot crosscheck, - bool wait, + int options, TM_FailureData *tmfd, LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes); @@ -629,6 +637,7 @@ typedef struct TableAmRoutine Relation OldIndex, bool use_sort, TransactionId OldestXmin, + Snapshot snapshot, TransactionId *xid_cutoff, MultiXactId *multi_cutoff, double *num_tuples, @@ -1459,6 +1468,7 @@ table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots, * cid - delete command ID (used for visibility test, and stored into * cmax if successful) * crosscheck - if not InvalidSnapshot, also check tuple against this + * XXX document options * wait - true if should wait for any conflicting update to commit/abort * changingPart - true iff the tuple is being moved to another partition * table due to an update of the partition key. Otherwise, false. @@ -1476,12 +1486,12 @@ table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots, */ static inline TM_Result table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, - Snapshot snapshot, Snapshot crosscheck, bool wait, - TM_FailureData *tmfd, bool changingPart) + Snapshot snapshot, Snapshot crosscheck, int options, + TM_FailureData *tmfd) { return rel->rd_tableam->tuple_delete(rel, tid, cid, snapshot, crosscheck, - wait, tmfd, changingPart); + options, tmfd); } /* @@ -1496,7 +1506,12 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, * cid - update command ID (used for visibility test, and stored into * cmax/cmin if successful) * crosscheck - if not InvalidSnapshot, also check old tuple against this - * wait - true if should wait for any conflicting update to commit/abort + * options - These allow the caller to specify options that may change the + * behavior of the AM. The AM will ignore options that it does not support. + * TABLE_UPDATE_WAIT -- set if should wait for any conflicting update to + * commit/abort + * TABLE_UPDATE_NO_LOGICAL -- force-disables the emitting of logical + * decoding information for the tuple. * * Output parameters: * slot - newly constructed tuple data to store @@ -1522,12 +1537,12 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, static inline TM_Result table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot, CommandId cid, Snapshot snapshot, Snapshot crosscheck, - bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, + int options, TM_FailureData *tmfd, LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes) { return rel->rd_tableam->tuple_update(rel, otid, slot, cid, snapshot, crosscheck, - wait, tmfd, + options, tmfd, lockmode, update_indexes); } @@ -1657,6 +1672,8 @@ table_relation_copy_data(Relation rel, const RelFileLocator *newrlocator) * not needed for the relation's AM * - *xid_cutoff - ditto * - *multi_cutoff - ditto + * - snapshot - if != NULL, ignore data changes done by transactions that this + * (MVCC) snapshot considers still in-progress or in the future. * * Output parameters: * - *xid_cutoff - rel's new relfrozenxid value, may be invalid @@ -1669,6 +1686,7 @@ table_relation_copy_for_cluster(Relation OldTable, Relation NewTable, Relation OldIndex, bool use_sort, TransactionId OldestXmin, + Snapshot snapshot, TransactionId *xid_cutoff, MultiXactId *multi_cutoff, double *num_tuples, @@ -1677,6 +1695,7 @@ table_relation_copy_for_cluster(Relation OldTable, Relation NewTable, { OldTable->rd_tableam->relation_copy_for_cluster(OldTable, NewTable, OldIndex, use_sort, OldestXmin, + snapshot, xid_cutoff, multi_cutoff, num_tuples, tups_vacuumed, tups_recently_dead); diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h index c79cd2d0e19..8ed7816e7b4 100644 --- a/src/include/commands/cluster.h +++ b/src/include/commands/cluster.h @@ -13,6 +13,8 @@ #ifndef CLUSTER_H #define CLUSTER_H +#include <signal.h> + #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -25,6 +27,7 @@ #define CLUOPT_RECHECK_ISCLUSTERED 0x04 /* recheck relation state for * indisclustered */ #define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */ +#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */ /* options for CLUSTER */ typedef struct ClusterParams @@ -32,11 +35,13 @@ typedef struct ClusterParams bits32 options; /* bitmask of CLUOPT_* */ } ClusterParams; +extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending; + extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel); extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid, - ClusterParams *params); + ClusterParams *params, bool isTopLevel); extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid, LOCKMODE lockmode); extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); @@ -48,8 +53,17 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool swap_toast_by_content, bool check_constraints, bool is_internal, + bool reindex, TransactionId frozenXid, MultiXactId cutoffMulti, char newrelpersistence); +extern void HandleRepackMessageInterrupt(void); +extern void ProcessRepackMessages(void); + +/* in repack_worker.c */ +extern void RepackWorkerMain(Datum main_arg); +extern bool IsRepackWorker(void); + + #endif /* CLUSTER_H */ diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h index 9c40772706c..b5b01c1bb6d 100644 --- a/src/include/commands/progress.h +++ b/src/include/commands/progress.h @@ -86,10 +86,12 @@ #define PROGRESS_REPACK_PHASE 1 #define PROGRESS_REPACK_INDEX_RELID 2 #define PROGRESS_REPACK_HEAP_TUPLES_SCANNED 3 -#define PROGRESS_REPACK_HEAP_TUPLES_WRITTEN 4 -#define PROGRESS_REPACK_TOTAL_HEAP_BLKS 5 -#define PROGRESS_REPACK_HEAP_BLKS_SCANNED 6 -#define PROGRESS_REPACK_INDEX_REBUILD_COUNT 7 +#define PROGRESS_REPACK_HEAP_TUPLES_INSERTED 4 +#define PROGRESS_REPACK_HEAP_TUPLES_UPDATED 5 +#define PROGRESS_REPACK_HEAP_TUPLES_DELETED 6 +#define PROGRESS_REPACK_TOTAL_HEAP_BLKS 7 +#define PROGRESS_REPACK_HEAP_BLKS_SCANNED 8 +#define PROGRESS_REPACK_INDEX_REBUILD_COUNT 9 /* * Phases of repack (as advertised via PROGRESS_REPACK_PHASE). @@ -98,9 +100,10 @@ #define PROGRESS_REPACK_PHASE_INDEX_SCAN_HEAP 2 #define PROGRESS_REPACK_PHASE_SORT_TUPLES 3 #define PROGRESS_REPACK_PHASE_WRITE_NEW_HEAP 4 -#define PROGRESS_REPACK_PHASE_SWAP_REL_FILES 5 -#define PROGRESS_REPACK_PHASE_REBUILD_INDEX 6 -#define PROGRESS_REPACK_PHASE_FINAL_CLEANUP 7 +#define PROGRESS_REPACK_PHASE_CATCH_UP 5 +#define PROGRESS_REPACK_PHASE_SWAP_REL_FILES 6 +#define PROGRESS_REPACK_PHASE_REBUILD_INDEX 7 +#define PROGRESS_REPACK_PHASE_FINAL_CLEANUP 8 /* Progress parameters for CREATE INDEX */ /* 3, 4 and 5 reserved for "waitfor" metrics */ diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h new file mode 100644 index 00000000000..d3402e0dedd --- /dev/null +++ b/src/include/commands/repack_internal.h @@ -0,0 +1,128 @@ +/*------------------------------------------------------------------------- + * + * repack_internal.h + * header for REPACK internals + * + * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group + * Portions Copyright (c) 1994-5, Regents of the University of California + * + * src/include/commands/repack_internal.h + * + *------------------------------------------------------------------------- + */ +#ifndef REPACK_INTERNAL_H +#define REPACK_INTERNAL_H + +#include "nodes/execnodes.h" +#include "replication/decode.h" +#include "postmaster/bgworker.h" +#include "replication/logical.h" +#include "storage/buffile.h" +#include "storage/sharedfileset.h" +#include "storage/shm_mq.h" +#include "utils/resowner.h" + +/* + * Stored as a single byte in the output file. + */ +typedef char ConcurrentChangeKind; + +#define CHANGE_INSERT 'i' +#define CHANGE_UPDATE_OLD 'u' +#define CHANGE_UPDATE_NEW 'U' +#define CHANGE_DELETE 'd' + +extern pid_t backend_pid; +extern ProcNumber backend_proc_number; + +/* + * Logical decoding state. + * + * The output plugin uses it to store the data changes that it decodes from + * WAL while the table contents is being copied to a new storage. + */ +typedef struct RepackDecodingState +{ +#ifdef USE_ASSERT_CHECKING + /* The relation whose changes we're decoding. */ + Oid relid; +#endif + + /* Per-change memory context. */ + MemoryContext change_cxt; + + /* A tuple slot used to pass tuples back and forth */ + TupleTableSlot *slot; + + /* + * Memory context and resource owner of the decoding worker's transaction. + */ + MemoryContext worker_cxt; + ResourceOwner worker_resowner; + + /* The current output file. */ + BufFile *file; +} RepackDecodingState; + +/* + * Layout of shared memory used for communication between backend and the + * worker that performs logical decoding of data changes + */ +typedef struct DecodingWorkerShared +{ + /* Is the decoding initialized? */ + bool initialized; + + /* + * Once the worker has reached this LSN, it should close the current + * output file and either create a new one or exit, according to the field + * 'done'. If the value is InvalidXLogRecPtr, the worker should decode all + * the WAL available and keep checking this field. It is ok if the worker + * had already decoded records whose LSN is >= lsn_upto before this field + * has been set. + */ + XLogRecPtr lsn_upto; + + /* Exit after closing the current file? */ + bool done; + + /* The output is stored here. */ + SharedFileSet sfs; + + /* Number of the last file exported by the worker. */ + int last_exported; + + /* Synchronize access to the fields above. */ + slock_t mutex; + + /* Database to connect to. */ + Oid dbid; + + /* Role to connect as. */ + Oid roleid; + + /* Decode data changes of this relation. */ + Oid relid; + + /* The backend uses this to wait for the worker. */ + ConditionVariable cv; + + /* Info to signal the backend. */ + PGPROC *backend_proc; + pid_t backend_pid; + ProcNumber backend_proc_number; + + /* + * Memory the queue is located in. + * + * For considerations on the value see the comments of + * PARALLEL_ERROR_QUEUE_SIZE. + */ +#define REPACK_ERROR_QUEUE_SIZE 16384 + char error_queue[FLEXIBLE_ARRAY_MEMBER]; +} DecodingWorkerShared; + +extern void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq); + + +#endif /* REPACK_INTERNAL_H */ diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h index 49f00fc48b8..02b5393474c 100644 --- a/src/include/replication/decode.h +++ b/src/include/replication/decode.h @@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx, XLogReaderState *record); +/* in commands/cluster.c */ +extern bool change_useless_for_repack(XLogRecordBuffer *buf); + + #endif diff --git a/src/include/replication/snapbuild.h b/src/include/replication/snapbuild.h index ccded021433..2b84f0058f0 100644 --- a/src/include/replication/snapbuild.h +++ b/src/include/replication/snapbuild.h @@ -72,7 +72,7 @@ extern void FreeSnapshotBuilder(SnapBuild *builder); extern void SnapBuildSnapDecRefcount(Snapshot snap); -extern Snapshot SnapBuildInitialSnapshot(SnapBuild *builder); +extern Snapshot SnapBuildInitialSnapshot(SnapBuild *builder, bool repack); extern const char *SnapBuildExportSnapshot(SnapBuild *builder); extern void SnapBuildClearExportedSnapshot(void); extern void SnapBuildResetExportedSnapshotState(void); diff --git a/src/include/storage/lockdefs.h b/src/include/storage/lockdefs.h index b73bb5618e6..3785b009808 100644 --- a/src/include/storage/lockdefs.h +++ b/src/include/storage/lockdefs.h @@ -36,8 +36,8 @@ typedef int LOCKMODE; #define AccessShareLock 1 /* SELECT */ #define RowShareLock 2 /* SELECT FOR UPDATE/FOR SHARE */ #define RowExclusiveLock 3 /* INSERT, UPDATE, DELETE */ -#define ShareUpdateExclusiveLock 4 /* VACUUM (non-FULL), ANALYZE, CREATE - * INDEX CONCURRENTLY */ +#define ShareUpdateExclusiveLock 4 /* VACUUM (non-exclusive), ANALYZE, CREATE + * INDEX CONCURRENTLY, REPACK CONCURRENTLY */ #define ShareLock 5 /* CREATE INDEX (WITHOUT CONCURRENTLY) */ #define ShareRowExclusiveLock 6 /* like EXCLUSIVE MODE, but allows ROW * SHARE */ diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h index 348fba53a93..a944ee0d211 100644 --- a/src/include/storage/procsignal.h +++ b/src/include/storage/procsignal.h @@ -36,6 +36,7 @@ typedef enum PROCSIG_BARRIER, /* global barrier interrupt */ PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */ PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */ + PROCSIG_REPACK_MESSAGE, /* Message from repack worker */ PROCSIG_RECOVERY_CONFLICT, /* backend is blocking recovery, check * PGPROC->pendingRecoveryConflicts for the * reason */ diff --git a/src/include/utils/snapmgr.h b/src/include/utils/snapmgr.h index 1c550096393..55ecc2c2054 100644 --- a/src/include/utils/snapmgr.h +++ b/src/include/utils/snapmgr.h @@ -78,6 +78,8 @@ extern Snapshot GetTransactionSnapshot(void); extern Snapshot GetLatestSnapshot(void); extern void SnapshotSetCommandId(CommandId curcid); +extern void FreeSnapshot(Snapshot snapshot); + extern Snapshot GetCatalogSnapshot(Oid relid); extern Snapshot GetNonHistoricCatalogSnapshot(Oid relid); extern void InvalidateCatalogSnapshot(void); diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile index a41d781f8c9..2cd7d87c533 100644 --- a/src/test/modules/injection_points/Makefile +++ b/src/test/modules/injection_points/Makefile @@ -14,6 +14,8 @@ REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress ISOLATION = basic \ inplace \ + repack \ + repack_toast \ syscache-update-pruned \ heap_lock_update diff --git a/src/test/modules/injection_points/expected/repack.out b/src/test/modules/injection_points/expected/repack.out new file mode 100644 index 00000000000..b575e9052ee --- /dev/null +++ b/src/test/modules/injection_points/expected/repack.out @@ -0,0 +1,113 @@ +Parsed test spec with 2 sessions + +starting permutation: wait_before_lock change_existing change_new change_subxact1 change_subxact2 check2 wakeup_before_lock check1 +injection_points_attach +----------------------- + +(1 row) + +step wait_before_lock: + REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey; + <waiting ...> +step change_existing: + UPDATE repack_test SET i=10 where i=1; + UPDATE repack_test SET j=20 where i=2; + UPDATE repack_test SET i=30 where i=3; + UPDATE repack_test SET i=40 where i=30; + DELETE FROM repack_test WHERE i=4; + +step change_new: + INSERT INTO repack_test(i, j) VALUES (5, 5), (6, 6), (7, 7), (8, 8); + UPDATE repack_test SET i=50 where i=5; + UPDATE repack_test SET j=60 where i=6; + DELETE FROM repack_test WHERE i=7; + +step change_subxact1: + BEGIN; + INSERT INTO repack_test(i, j) VALUES (100, 100); + SAVEPOINT s1; + UPDATE repack_test SET i=101 where i=100; + SAVEPOINT s2; + UPDATE repack_test SET i=102 where i=101; + COMMIT; + +step change_subxact2: + BEGIN; + SAVEPOINT s1; + INSERT INTO repack_test(i, j) VALUES (110, 110); + ROLLBACK TO SAVEPOINT s1; + INSERT INTO repack_test(i, j) VALUES (110, 111); + COMMIT; + +step check2: + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_test'; + + SELECT i, j FROM repack_test ORDER BY i, j; + + INSERT INTO data_s2(i, j) + SELECT i, j FROM repack_test; + + i| j +---+--- + 2| 20 + 6| 60 + 8| 8 + 10| 1 + 40| 3 + 50| 5 +102|100 +110|111 +(8 rows) + +step wakeup_before_lock: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step wait_before_lock: <... completed> +step check1: + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_test'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + SELECT i, j FROM repack_test ORDER BY i, j; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_test; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; + +count +----- + 2 +(1 row) + + i| j +---+--- + 2| 20 + 6| 60 + 8| 8 + 10| 1 + 40| 3 + 50| 5 +102|100 +110|111 +(8 rows) + +count +----- + 0 +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/src/test/modules/injection_points/expected/repack_toast.out b/src/test/modules/injection_points/expected/repack_toast.out new file mode 100644 index 00000000000..b56dde134f8 --- /dev/null +++ b/src/test/modules/injection_points/expected/repack_toast.out @@ -0,0 +1,65 @@ +Parsed test spec with 2 sessions + +starting permutation: wait_before_lock change check2 wakeup_before_lock check1 +injection_points_attach +----------------------- + +(1 row) + +step wait_before_lock: + REPACK (CONCURRENTLY) repack_test; + <waiting ...> +step change: + UPDATE repack_test SET j=get_long_string() where i=2; + DELETE FROM repack_test WHERE i=3; + INSERT INTO repack_test(i, j) VALUES (4, get_long_string()); + UPDATE repack_test SET i=3 where i=1; + +step check2: + INSERT INTO relfilenodes(node) + SELECT c2.relfilenode + FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid + WHERE c1.relname='repack_test'; + + INSERT INTO data_s2(i, j) + SELECT i, j FROM repack_test; + +step wakeup_before_lock: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step wait_before_lock: <... completed> +step check1: + INSERT INTO relfilenodes(node) + SELECT c2.relfilenode + FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid + WHERE c1.relname='repack_test'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_test; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; + +count +----- + 4 +(1 row) + +count +----- + 0 +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build index fcc85414515..a414abb924b 100644 --- a/src/test/modules/injection_points/meson.build +++ b/src/test/modules/injection_points/meson.build @@ -45,6 +45,8 @@ tests += { 'specs': [ 'basic', 'inplace', + 'repack', + 'repack_toast', 'syscache-update-pruned', 'heap_lock_update', ], diff --git a/src/test/modules/injection_points/specs/repack.spec b/src/test/modules/injection_points/specs/repack.spec new file mode 100644 index 00000000000..d727a9b056b --- /dev/null +++ b/src/test/modules/injection_points/specs/repack.spec @@ -0,0 +1,142 @@ +# REPACK (CONCURRENTLY) ... USING INDEX ...; +setup +{ + CREATE EXTENSION injection_points; + + CREATE TABLE repack_test(i int PRIMARY KEY, j int); + INSERT INTO repack_test(i, j) VALUES (1, 1), (2, 2), (3, 3), (4, 4); + + CREATE TABLE relfilenodes(node oid); + + CREATE TABLE data_s1(i int, j int); + CREATE TABLE data_s2(i int, j int); +} + +teardown +{ + DROP TABLE repack_test; + DROP EXTENSION injection_points; + + DROP TABLE relfilenodes; + DROP TABLE data_s1; + DROP TABLE data_s2; +} + +session s1 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +# Perform the initial load and wait for s2 to do some data changes. +step wait_before_lock +{ + REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey; +} +# Check the table from the perspective of s1. +# +# Besides the contents, we also check that relfilenode has changed. + +# Have each session write the contents into a table and use FULL JOIN to check +# if the outputs are identical. +step check1 +{ + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_test'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + SELECT i, j FROM repack_test ORDER BY i, j; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_test; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); +} + +session s2 +# Change the existing data. UPDATE changes both key and non-key columns. Also +# update one row twice to test whether tuple version generated by this session +# can be found. +step change_existing +{ + UPDATE repack_test SET i=10 where i=1; + UPDATE repack_test SET j=20 where i=2; + UPDATE repack_test SET i=30 where i=3; + UPDATE repack_test SET i=40 where i=30; + DELETE FROM repack_test WHERE i=4; +} +# Insert new rows and UPDATE / DELETE some of them. Again, update both key and +# non-key column. +step change_new +{ + INSERT INTO repack_test(i, j) VALUES (5, 5), (6, 6), (7, 7), (8, 8); + UPDATE repack_test SET i=50 where i=5; + UPDATE repack_test SET j=60 where i=6; + DELETE FROM repack_test WHERE i=7; +} + +# When applying concurrent data changes, we should see the effects of an +# in-progress subtransaction. +# +# XXX Not sure this test is useful now - it was designed for the patch that +# preserves tuple visibility and which therefore modifies +# TransactionIdIsCurrentTransactionId(). +step change_subxact1 +{ + BEGIN; + INSERT INTO repack_test(i, j) VALUES (100, 100); + SAVEPOINT s1; + UPDATE repack_test SET i=101 where i=100; + SAVEPOINT s2; + UPDATE repack_test SET i=102 where i=101; + COMMIT; +} + +# When applying concurrent data changes, we should not see the effects of a +# rolled back subtransaction. +# +# XXX Is this test useful? See above. +step change_subxact2 +{ + BEGIN; + SAVEPOINT s1; + INSERT INTO repack_test(i, j) VALUES (110, 110); + ROLLBACK TO SAVEPOINT s1; + INSERT INTO repack_test(i, j) VALUES (110, 111); + COMMIT; +} + +# Check the table from the perspective of s2. +step check2 +{ + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_test'; + + SELECT i, j FROM repack_test ORDER BY i, j; + + INSERT INTO data_s2(i, j) + SELECT i, j FROM repack_test; +} +step wakeup_before_lock +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +# Test if data changes introduced while one session is performing REPACK +# CONCURRENTLY find their way into the table. +permutation + wait_before_lock + change_existing + change_new + change_subxact1 + change_subxact2 + check2 + wakeup_before_lock + check1 diff --git a/src/test/modules/injection_points/specs/repack_toast.spec b/src/test/modules/injection_points/specs/repack_toast.spec new file mode 100644 index 00000000000..b878b198971 --- /dev/null +++ b/src/test/modules/injection_points/specs/repack_toast.spec @@ -0,0 +1,112 @@ +# REPACK (CONCURRENTLY); +# +# Test handling of TOAST. At the same time, no tuplesort. +setup +{ + CREATE EXTENSION injection_points; + + -- Return a string that needs to be TOASTed. + CREATE FUNCTION get_long_string() + RETURNS text + LANGUAGE sql as $$ + SELECT string_agg(chr(65 + trunc(25 * random())::int), '') + FROM generate_series(1, 2048) s(x); + $$; + + CREATE TABLE repack_test(i int PRIMARY KEY, j text); + INSERT INTO repack_test(i, j) VALUES (1, get_long_string()), + (2, get_long_string()), (3, get_long_string()); + + CREATE TABLE relfilenodes(node oid); + + CREATE TABLE data_s1(i int, j text); + CREATE TABLE data_s2(i int, j text); +} + +teardown +{ + DROP TABLE repack_test; + DROP EXTENSION injection_points; + DROP FUNCTION get_long_string(); + + DROP TABLE relfilenodes; + DROP TABLE data_s1; + DROP TABLE data_s2; +} + +session s1 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +# Perform the initial load and wait for s2 to do some data changes. +step wait_before_lock +{ + REPACK (CONCURRENTLY) repack_test; +} +# Check the table from the perspective of s1. +# +# Besides the contents, we also check that relfilenode has changed. + +# Have each session write the contents into a table and use FULL JOIN to check +# if the outputs are identical. +step check1 +{ + INSERT INTO relfilenodes(node) + SELECT c2.relfilenode + FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid + WHERE c1.relname='repack_test'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_test; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); +} + +session s2 +step change +# Separately test UPDATE of both plain ("i") and TOASTed ("j") attribute. In +# the first case, the new tuple we get from reorderbuffer.c contains "j" as a +# TOAST pointer, which we need to update so it points to the new heap. In the +# latter case, we receive "j" as "external indirect" value - here we test that +# the decoding worker writes the tuple to a file correctly and that the +# backend executing REPACK manages to restore it. +{ + UPDATE repack_test SET j=get_long_string() where i=2; + DELETE FROM repack_test WHERE i=3; + INSERT INTO repack_test(i, j) VALUES (4, get_long_string()); + UPDATE repack_test SET i=3 where i=1; +} +# Check the table from the perspective of s2. +step check2 +{ + INSERT INTO relfilenodes(node) + SELECT c2.relfilenode + FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid + WHERE c1.relname='repack_test'; + + INSERT INTO data_s2(i, j) + SELECT i, j FROM repack_test; +} +step wakeup_before_lock +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +# Test if data changes introduced while one session is performing REPACK +# CONCURRENTLY find their way into the table. +permutation + wait_before_lock + change + check2 + wakeup_before_lock + check1 diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..84d61f3ab34 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2021,7 +2021,7 @@ pg_stat_progress_cluster| SELECT pid, phase, repack_index_relid AS cluster_index_relid, heap_tuples_scanned, - heap_tuples_written, + (heap_tuples_inserted + heap_tuples_updated) AS heap_tuples_written, heap_blks_total, heap_blks_scanned, index_rebuild_count @@ -2101,17 +2101,20 @@ pg_stat_progress_repack| SELECT s.pid, WHEN 2 THEN 'index scanning heap'::text WHEN 3 THEN 'sorting tuples'::text WHEN 4 THEN 'writing new heap'::text - WHEN 5 THEN 'swapping relation files'::text - WHEN 6 THEN 'rebuilding index'::text - WHEN 7 THEN 'performing final cleanup'::text + WHEN 5 THEN 'catch-up'::text + WHEN 6 THEN 'swapping relation files'::text + WHEN 7 THEN 'rebuilding index'::text + WHEN 8 THEN 'performing final cleanup'::text ELSE NULL::text END AS phase, (s.param3)::oid AS repack_index_relid, s.param4 AS heap_tuples_scanned, - s.param5 AS heap_tuples_written, - s.param6 AS heap_blks_total, - s.param7 AS heap_blks_scanned, - s.param8 AS index_rebuild_count + s.param5 AS heap_tuples_inserted, + s.param6 AS heap_tuples_updated, + s.param7 AS heap_tuples_deleted, + s.param8 AS heap_blks_total, + s.param9 AS heap_blks_scanned, + s.param10 AS index_rebuild_count FROM (pg_stat_get_progress_info('REPACK'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20) LEFT JOIN pg_database d ON ((s.datid = d.oid))); pg_stat_progress_vacuum| SELECT s.pid, diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index e3c1007abdf..cab34d68054 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -428,6 +428,7 @@ CatCacheHeader CatalogId CatalogIdMapEntry CatalogIndexState +ChangeContext ChangeVarNodes_callback ChangeVarNodes_context ChannelName @@ -505,6 +506,7 @@ CompressFileHandle CompressionLocation CompressorState ComputeXidHorizonsResult +ConcurrentChangeKind ConditionVariable ConditionVariableMinimallyPadded ConditionalStack @@ -646,6 +648,8 @@ DeclareCursorStmt DecodedBkpBlock DecodedXLogRecord DecodingOutputState +DecodingWorker +DecodingWorkerShared DefElem DefElemAction DefaultACLInfo @@ -1305,6 +1309,7 @@ IndexElem IndexFetchHeapData IndexFetchTableData IndexInfo +IndexInsertState IndexList IndexOnlyScan IndexOnlyScanState @@ -2619,6 +2624,7 @@ ReorderBufferTupleCidKey ReorderBufferUpdateProgressTxnCB ReorderTuple RepackCommand +RepackDecodingState RepackStmt ReparameterizeForeignPathByChild_function ReplOriginId -- 2.47.3 --mdsv5bdgfjjj6d77 Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="v46-0003-Use-BulkInsertState-when-copying-data-to-the-new.patch" ^ permalink raw reply [nested|flat] 50+ messages in thread
end of thread, other threads:[~2026-03-11 14:16 UTC | newest] Thread overview: 50+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2017-03-03 07:39 [PATCH 1/3] Rewrite sections in ddl.sgml related to partitioning amit <[email protected]> 2026-03-11 14:16 [PATCH v46 2/7] Add CONCURRENTLY option to REPACK command. Antonin Houska <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox