Re: Declarative partitioning
Re: Declarative partitioning
От:
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Дата:
On 2016/06/08 22:22, Ashutosh Bapat wrote: > On Mon, May 23, 2016 at 3:35 PM, Amit Langote wrote >> >> [...] >> >> I made a mistake in the last version of the patch which caused a relcache >> field to be pfree'd unexpectedly. Attached updated patches. > > 0003-... patch does not apply cleanly. It has some conflicts in pg_dump.c. > I have tried fixing the conflict in attached patch. Thanks. See attached rebased patches. Regards, Amit
Declarative partitioning
От:
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Дата:
Hi,
I would like propose $SUBJECT for this development cycle. Attached is a
WIP patch that implements most if not all of what's described below. Some
yet unaddressed parts are mentioned below, too. I'll add this to the CF-SEP.
Syntax
======
1. Creating a partitioned table
CREATE TABLE table_name
PARTITION BY {RANGE|LIST}
ON (column_list);
Where column_list consists of simple column names or expressions:
PARTITION BY LIST ON (name)
PARTITION BY RANGE ON (year, month)
PARTITION BY LIST ON ((lower(left(name, 2)))
PARTITION BY RANGE ON ((extract(year from d)), (extract(month from d)))
Note: LIST partition key supports only one column.
For each column, you could write operator class name:
PARTITION BY LIST/RANGE ON (colname [USING] opclass_name),
If not specified, the default btree operator class based on type of each
key column is used. If none of the available btree operator classes are
compatible with the partitioning strategy (list/range), error is thrown.
Built-in btree operator classes cover a good number of types for list and
range partitioning in practical scenarios.
A table created using this form is of proposed new relkind
RELKIND_PARTITIONED_REL. An entry in pg_partitioned_rel (see below) is
created to store partition key info.
Note: A table cannot be partitioned after-the-fact using ALTER TABLE.
Normal dependencies are created between the partitioned table and operator
classes, object in partition expressions like functions.
2. Creating a partition of a partitioned table
CREATE TABLE table_name
PARTITION OF partitioned_table_name
FOR VALUES values_spec;
Where values_spec is:
listvalues: [IN] (val1, ...)
rangevalues: START (col1min, ... ) END (col1max, ... )
| START (col1min, ... )
| END (col1max, ... )
A table created using this form has proposed pg_class.relispartition set
to true. An entry in pg_partition (see below) is created to store the
partition bound info.
The values_spec should match the partitioning strategy of the partitioned
table. In case of a range partition, the values in START and/or END should
match columns in the partition key.
Defining a list partition is fairly straightforward - just spell out the
list of comma-separated values. Error is thrown if the list of values
overlaps with one of the existing partitions' list.
CREATE TABLE persons_by_state (name text, state text)
PARTITION BY LIST ON (state);
CREATE TABLE persons_IL
PARTITION OF persons_by_state
FOR VALUES IN ('IL');
CREATE TABLE persons_fail
PARTITION OF persons_by_state
FOR VALUES IN ('IL');
ERROR: cannot create partition that overlaps with an existing one
For a range partition, there are more than one way:
Specify both START and END bounds: resulting range should not overlap with
the range(s) covered by existing partitions. Error is thrown otherwise.
Although rare in practice, gaps between ranges are OK.
CREATE TABLE measurement(logdate date NOT NULL)
PARTITION BY RANGE ON (logdate);
CREATE TABLE measurement_y2006m02
PARTITION OF measurement
FOR VALUES START ('2006-02-01') END ('2006-03-01'); --success
CREATE TABLE measurement_fail
PARTITION OF measurement
FOR VALUES START ('2006-02-15') END ('2006-03-01');
ERROR: cannot create partition that overlaps with an existing one
Specify only the START bound: add the partition on the left of some range
covered by existing partitions provided no overlap occurs (also
considering gaps between ranges, if any). If no such range exists, the new
partition will cover the range [START, +INFINITY) and become the rightmost
partition. Error is thrown if the specified START causes overlap.
CREATE TABLE measurement_y2006m01
PARTITION OF measurement
FOR VALUES START ('2006-01-01'); --success
CREATE TABLE measurement_fail
PARTITION OF measurement
FOR VALUES START ('2006-02-01'); --overlaps with measurement_y2006m02
ERROR: cannot create partition that overlaps with an existing one
Specify only the END bound: add the partition on the right of some range
covered by existing partitions provided no overlap occurs (also
considering gaps between ranges, if any). If no such range exists, the new
partition would cover the range (-INFINITY, END) and become the leftmost
partition. Error is thrown if the specified END causes overlap.
CREATE TABLE measurement_y2006m03
PARTITION OF measurement
FOR VALUES END ('2006-04-01'); --success
CREATE TABLE measurement_fail
PARTITION OF measurement
FOR VALUES END ('2006-03-01'); --overlaps with measurement_y2006m02
ERROR: cannot create partition that overlaps with an existing one
For each partition, START and END bound values are stored in the
catalog. Note that the lower bound is inclusive, whereas the upper bound
is exclusive.
Note: At most one range partition can have null min bound in which case it
covers the range (-INFINITY, END). Also, at most one range partition can
have null max bound in which case it covers the range [START, +INFINITY).
A normal dependency is created between the parent and the partition.
3. Multi-level partitioning
CREATE TABLE table_name
PARTITION OF partitioned_table_name
FOR VALUES values_spec
PARTITION BY {RANGE|LIST} ON (columns_list)
This variant implements a form of so called composite or sub-partitioning
with arbitrarily deep partitioning structure. A table created using this
form has both the relkind RELKIND_PARTITIONED_REL and
pg_class.relispartition set to true.
4. (yet unimplemented) Attach partition (from existing table)
ALTER TABLE partitioned_table
ATTACH PARTITION partition_name
FOR VALUES values_spec
USING [TABLE] table_name;
ALTER TABLE table_name
SET VALID PARTITION OF ;
The first of the above pair of commands would attach table_name as a (yet)
'invalid' partition of partitioned_table (after confirming that it matches
the schema and does not overlap with other partitions per FOR VALUES
spec). It would also record the FOR VALUES part in the partition catalog
and set pg_class.relispartition to true for table_name.
After the first command is done, the second command would take exclusive
lock on table_name, scan the table to check if it contains any values
outside the boundaries defined by FOR VALUES clause defined previously,
throw error if so, mark as valid partition of parent if not.
Does that make sense?
5. Detach partition
ALTER TABLE partitioned_table
DETACH PARTITION partition_name [USING table_name]
This removes partition_name as partition of partitioned_table. The table
continues to exist with the same name or 'table_name', if specified.
pg_class.relispartition is set to false for the table, so it behaves like
a normal table.
System catalogs
===============
1. pg_partitioned_rel
CATALOG(pg_partitioned_rel,oid) BKI_WITHOUT_OIDS
{
Oid partrelid; /* partitioned table pg_class.oid */
char partstrategy; /* partitioning strategy 'l'/'r' */
int16 partnatts; /* number of partition columns */
int2vector partkey; /* column numbers of partition columns;
* 0 where specified column is an
* expresion */
oidvector partclass; /* operator class to compare keys */
pg_node_tree partexprs; /* expression trees for partition key
* members that are not simple column
* references; one for each zero entry
* in partkey[] */
};
2. pg_partition (omits partisvalid alluded to above)
CATALOG(pg_partition,oid) BKI_WITHOUT_OIDS
{
Oid partitionid; /* partition oid */
Oid partparent; /* parent oid */
anyarray partlistvalues; /* list of allowed values of the only
* partition column */
anyarray partrangebounds; /* list of bounds of ranges of
* allowed values per partition key
* column */
};
Further notes
=============
There are a number of restrictions on performing after-the-fact changes
using ALTER TABLE to partitions (ie, relispartition=true):
* Cannot add/drop column
* Cannot set/drop OIDs
* Cannot set/drop NOT NULL
* Cannot set/drop default
* Cannot alter column type
* Cannot add/drop alter constraint (table level)
* Cannot change persistence
* Cannot change inheritance
* Cannot link to a composite type
Such changes should be made to the topmost parent in the partitioning
hierarchy (hereafter referred to as just parent). These are recursively
applied to all the tables in the hierarchy. Although the last two items
cannot be performed on parent either.
Dropping a partition using DROP TABLE is not allowed. It needs to detached
using ALTER TABLE on parent before it can be dropped as a normal table.
Triggers on partitions are not allowed. They should be defined on the
parent. That said, I could not figure out a way to implement row-level
AFTER triggers on partitioned tables (more about that in a moment); so
they are currently not allowed:
CREATE TRIGGER audit_trig
AFTER INSERT ON persons
FOR EACH ROW EXECUTE PROCEDURE audit_func();
ERROR: Row-level AFTER triggers are not supported on partitioned tables
Column/table constraints on partitions are not allowed. They should be
defined on the parent. Foreign key constraints are not allowed due to
above limitation (no row-level after triggers).
A partitioning parent (RELKIND_PARTITIONED_REL) does not have storage.
Creating index on parent is not allowed. They should be defined on (leaf)
partitions. Because of this limitation, primary keys are not allowed on a
partitioned table. Perhaps, we should be able to just create a dummy
entry somewhere to represent an index on parent (which every partition
then copies.) Then by restricting primary key to contain all partition key
columns, we can implement unique constraint over the whole partitioned
table. That will in turn allow us to use partitioned tables as PK rels in
a foreign key constraint provided row-level AFTER trigger issue is resolved.
VACUUM/ANALYZE on individual partitions should work like normal tables.
I've not implemented something like inheritance tree sampling for
partitioning tree in this patch. Autovacuum has been taught to ignore
parent tables and vacuum/analyze partitions normally.
Dropping a partitioned table should (?) unconditionally drop all its
partitions probably but, currently the patch uses dependencies, so
requires to specify CASCADE to do the same.
What should TRUNCATE on partitioned table do?
Ownership, privileges/permissions, RLS should be managed through the
parent table although not comprehensively addressed in the patch.
There is no need to define tuple routing triggers. CopyFrom() and
ExecInsert() determine target partition just before performing
heap_insert() and ExecInsertIndexTuples(). IOW, any BR triggers and
constraints (on parent) are executed for tuple before being routed to a
partition. If no partition can be found, it's an error.
Because row-level AFTER triggers need to save ItemPointers in trigger
event data and defining triggers on partitions (which is where tuples
really go) is not allowed, I could not find a straightforward way to
implement them. So, perhaps we should allow (only) row-level AFTER
triggers on partitions or think of modifying trigger.c to know about this
twist explicitly.
Internal representations
========================
For a RELKIND_PARTITIONED_REL relations, RelationData gets a few new
fields to store the partitioning metadata. That includes partition key
tuple (pg_partitioned_rel) including some derived info (opfamily,
opcintype, compare proc FmgrInfos, partition expression trees).
Additionally, it also includes a PartitionInfo object which includes
partition OIDs array, partition bound arrays (if range partitioned,
rangemax is sorted in ascending order and OIDs are likewise ordered). It
is built from information in pg_partition catalog.
While RelationBuildDesc() initializes the basic key info, fields like
expression trees, PartitionInfo are built on demand and cached. For
example, InitResultRelInfo() builds the latter to populate the newly added
ri_PartitionKeyInfo and ri_Partitions fields, respectively.
PartitionInfo object is rebuilt on every cache invalidation of the rel
which includes when adding/attaching/detaching a new partition.
Planner and executor considerations
=====================================
The patch does not yet implement any planner changes for partitioned
tables, although I'm working on the same and post updates as soon as
possible. That means, it is not possible to run SELECT/UPDATE/DELETE
queries on partitioned tables without getting:
postgres=# SELECT * FROM persons;
ERROR: could not open file "base/13244/106975": No such file or directory
Given that there would be more direct ways of performing partition pruning
decisions with the proposed, it would be nice to utilize them.
Specifically, I would like to avoid having to rely on constraint exclusion
for partition pruning whereby subquery_planner() builds append_rel_list
and the later steps exclude useless partitions.
By extending RelOptInfo to include partitioning info for partitioned rels,
it might be possible to perform partition pruning directly without
previously having to expand them. Although, as things stand now, it's not
clear how that might work - when would partition RTEs be added to the
rtable? The rtable is assumed not to change after
setup_simple_rel_arrays() has done its job which is much earlier than when
it would be desirable for the partitioned table expansion (along with
partition pruning) to happen. Moreover, if that means we might not be able
to build RelOptInfo's for partitions, how to choose best paths for them
(index paths or not, etc.)?
I'm also hoping we don't require something like inheritance_planner() for
when partitioned tables are target rels. I assume considerations for why
the special processing is necessary for inheritance trees in that scenario
don't apply to partitioning trees. So, if grouping_planner() returns a
Append plan (among other options) for the partitioning tree, tacking a
ModifyTable node on top should do the trick?
Suggestions greatly welcome in this area.
Other items
===========
Will include the following once we start reaching consensus on main parts
of the proposed design/implementation:
* New regression tests
* Documentation updates
* pg_dump, psql, etc.
For reference, some immediately previous discussions:
* On partitioning *
http://www.postgresql.org/message-id/20140829155607.GF7705@eldon.alvh.no-ip.org
* Partitioning WIP patch *
http://www.postgresql.org/message-id/54EC32B6.9070605@lab.ntt.co.jp
Comments welcome!
Thanks,
Amit
Re: Declarative partitioning
От:
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Дата:
On 2015/08/26 23:01, Simon Riggs wrote: > The following review has been posted through the commitfest application: > ... > > Needs planner work and tests of that. ALTER TABLE etc can wait. > > The new status of this patch is: Waiting on Author Alright, after spending much time on this I came up with the attached. I'm sincerely sorry this took so long. I'll describe below what's in those patches. The DDL and catalogs part are not much different from what I had last described though I took a few steps to simplify things. I dropped the multi-level partitioning bit and changed range partition creation syntax a little. Ability to specify both min and max for every range partition had made life much difficult to construct and use the internal representation. Anyway, attached document patch (and regression tests to some degree) might give a picture of what user commands look like as of now, although things are very much subject to overhaul in this general area of user visible commands and features. As pointed out by Simon, there is a need to invent a better internal representation of partitioning constraints/metadata to address the planning considerations more effectively and scalably. To that end, I have tried to implement a number of things - There is a proposed set_partitioned_rel_size() that calls planner's partitioning module (proposed) which takes RelOptInfo of a partitioned table and returns a list of OIDs of partitions that are selected to be scanned based on the baserestrictinfo (other partitions are pruned!) The partitioned rel size is then computed using size estimates for selected partitions. Later, proposed set_partitioned_rel_pathlist() will create a Append/MergeAppend path with cheapest paths for individual partitions as its subpaths. It seems important to point out that the selected partitions each get simple_rte_array and a simple_rel_array slots (note that those arrays are required to be repalloc'd to make those slots available). The partition reloptinfos are of kind RELOPT_PARTITION_REL which are functionally quite similar to RELOPT_OTHER_MEMBER_REL but different in that they do not require using any auxiliary structures like AppendRelInfo and append_rel_list. The parent-partition mapping would still be necessary and is achieved using a field called parent_relid of partition reloptinfo. The proposed partitioning module (in the planner) figures out which of the clauses to use for partition pruning. The rules of matching clauses to partition key look similar to those used to match them to an index, at least with respect to the key representation. Key info includes attribute number, expression tree (for expression key columns), operator family OID, per key column. While clauses matched to an index would be returned as it is to make index paths with, things work differently for clauses matched to partition key column(s). For each matched clause, the Const operand and the operator would be added to operand list and operator list, respectively, for the matched column. Operands are later evaluated to datums. The datum arrays along with operator OIDs are passed along to another (general-purpose) partitioning module (proposed). That module returns a list of OIDs of selected partitions after comparing the clause operand datums with the partition bounds. The operator strategies (of passed operators) coupled with partitioning method (list or range, currently) determine how partitions are selected. For some partitioning methods, faster partition selection is enabled, for example, being able to use binary search helps make selecting range partitions reasonably fast. Note that the clauses matched for a given column are effectively ANDed with one another. At the moment, using OR clauses disables partition pruning. But it would be a matter of improving the structure used to pass around the operands and operators to also convey the logical structure of clauses. Also, even though range partitioning allows multi-column key, only the clauses referencing the first key are used to select partitions. Multi-column range partition pruning implementation is saved for later. Note that partition bounds are themselves stored as arrays of datums computed on-demand (cached in RelationData as part of a structure called PartitionDesc which stores other info about partitions like OIDs.) There are not many executor changes currently except for those made to ensure sane I/U/D behaviors (made to respective ExecI/U/D functions). We can certainly think about implementing specialized scan node for partitioned tables but I put it off for now. Attached user documentation and regression test patches, though not quite comprehensive, should help one get started. Thanks, Amit
Re: Declarative partitioning
От:
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Дата:
Hi,
Attached find a patch series to implement the following syntax:
1. Syntax for defining the partition key as a new clause in CREATE TABLE:
[ PARTITION BY {RANGE | LIST} ON ( { column_name | ( expression ) } [
opclass ] [, ...] )
[ SUBPARTITION BY {RANGE | LIST} ON ( { column_name | ( expression ) }
[ opclass ] [, ...] ) ] ]
2. Syntax to create partitions and sub-partitions (as ALTER TABLE commands
on a partitioned table):
ADD PARTITION name FOR VALUES partition_bound_spec
[ WITH ( storage_parameter [= value] [, ... ] )]
[ TABLESPACE tablespace_name ]
MODIFY PARTITION partition_name ADD SUBPARTITION name FOR VALUES
partition_bound_spec
[ WITH ( storage_parameter [= value] [, ... ] )]
[ TABLESPACE tablespace_name ]
DROP PARTITION name
DROP SUBPARTITION name
ATTACH PARTITION name FOR VALUES partition_bound_spec
USING [ TABLE ] table_name
[ WITH ( storage_parameter [= value] [, ... ] )]
[ TABLESPACE tablespace_name ]
MODIFY PARTITION partition_name ATTACH SUBPARTITION name FOR VALUES
partition_bound_spec
USING [ TABLE ] table_name
[ WITH ( storage_parameter [= value] [, ... ] )]
[ TABLESPACE tablespace_name ]
Where partition_bound_spec is:
FOR VALUES [ IN ] ( expression [, ...] )
FOR VALUES LESS THAN ( expression [, ...] )
DETACH PARTITION name [ USING [ TABLE ] table_name]
DETACH SUBPARTITION name [ USING [ TABLE ] table_name]
Please note that ATTACH PARTITION ... USING TABLE command shown above is
not yet implemented.
As mentioned previously, this uses two system catalogs pg_partitioned_rel
and pg_partition.
This is complete with tuple routing so that no triggers or rules are
required. There is also basic planner support but no support yet to enable
constraint exclusion on partitions (which will be fulfilled shortly by
installing equivalent check constraints on partitions).
There is documentation for the new syntax and catalogs. I feel more
detailed documentation is required so I will keep on improving it in
subsequent versions as we build consensus about the syntax and general
design. There are no regression tests at all in the attached patches which
I will add in the next version along with constraint exclusion support.
pg_dump support will also be added shortly.
The individual patches have commit messages that describe code changes.
This is registered in the upcoming CF. Feedback and review is greatly
welcomed!
Thanks,
Amit
Re: Declarative partitioning
От:
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Дата:
On 2016/02/15 10:55, Amit Langote wrote: > required. There is also basic planner support but no support yet to enable > constraint exclusion on partitions (which will be fulfilled shortly by > installing equivalent check constraints on partitions). Just to follow up on this - attached now adds equivalent check constraint with partition creation. Constraint exclusion should work. No regression tests yet though. >From now on, instead of attaching multiple files like in the previous message, I will send a single tar.gz which will contain patches created by git-format-patch. Thanks, Amit
Re: Declarative partitioning
От:
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Дата:
Hi Ashutosh, On 2016/04/14 21:34, Ashutosh Bapat wrote: > Hi Amit, > Here are some random comments. You said that you were about to post a new > patch, so you might have already taken care of those comments. But anyway > here they are. Thanks a lot for the comments. The patch set changed quite a bit since the last version. Once the CF entry was marked returned with feedback on March 22, I held off sending the new version at all. Perhaps, it would have been OK. Anyway here it is, if you are interested. I will create an entry in CF 2016-09 for the same. Also, see below replies to you individual comments. > 1. Following patches do not apply cleanly for me. > 0001 > 0003 - conflicts at #include for partition.h in rel.h. > 0004 - conflicts in src/backend/catalog/Makefile > 0005 - conflicts in src/backend/parser/gram.y These should be fixed although the attached one is a significantly different patch set. > 2. The patches are using now used OIDs 3318-3323. Corresponding objects > need new unused oids. Right, fixed. > 3. In patch 0001-*, you are using indkey instead of partkey in one of the > comments and also in documentation. Fixed. > 4. After all patches are applied I am getting several errors like "error: > #error "lock.h may not be included from frontend code", while building > rmgrdesc.c. This > seems to be because rel.h includes partition.h, which leads to inclusion of > lock.h in rmgrdesc.c. Are you getting the same error message? It looks like > we need separate header file for declaring function which can be used at > the time of execution, which is anyway better irrespective of the compiler > error. Yeah, I too am beginning to feel that dividing partition.h into separate headers would be a good idea in long term. For time being, I have managed to get rid of partition.h #included in rel.h which solves the issue. > 5. In expand_partitioned_rtentry(), instead of finding all the leaf level > relations, it might be better to build the RTEs, RelOptInfo and paths for > intermediate relations as well. This helps in partition pruning. In your > introductory write-up you have mentioned this. It might be better if v1 > includes this change, so that the partition hierarchy is visible in EXPLAIN > output as well. > > 6. Explain output of scan on a partitioned table shows Append node with > individual table scans as sub-plans. May be we should annotate Append node > with > the name of the partitioned table to make EXPLAIN output more readable. I got rid of all the optimizer changes in the new version (except a line or two). I did that by switching to storing parent-child relationships in pg_inherits so that all the existing optimizer code for inheritance sets works unchanged. Also, the implementation detail that required to put only leaf partitions in the append list is also gone. Previously no storage was allocated for partitioned tables (either root or any of the internal partitions), so it was being done that way. Regarding 6, it seems to me that because Append does not have a associated relid (like scan nodes have with scanrelid). Maybe we need to either fix Append or create some enhanced version of Append which would also support dynamic pruning. > 7. \d+ output of partitioned table does not show partitioning information, > something necessary for V1. This has been fixed in the attached. > 8. Instead of storing partition key column numbers and expressions > separately, can we store everything as expression; columns being a single > Var node expression? That will avoid constructing Var nodes to lookup in > equivalence classes for partition pruning or join optimizations. Hmm, let me consider that. FWIW, if you look at my proposed patch (or description thereof) back in CF 2015-11 [1], you will see that I had modeled matching clauses to partition key on lines of how similar processing is done for indexes (as in how indexes are matched with clauses - down all the way to match_clause_to_indexcol()). None of that exists in the current patch set but when we get to that (optimizer patch that is), I perhaps wouldn't do it radically differently. I admit however that I hadn't thought really hard about join optimization stuff so I may be missing something. > 10. The code distinguishes between the top level table and its partitions > which in turn are partitioned. We should try to minimize this distinction > as much as possible so as to use recursive functions for operating on > partitions. E.g. each of the partitioned partitions may be labelled > RELKIND_PARTITIONED_REL? A 0/NULL parent would distinguish between root > partition and child partitions. Exactly how it's done in the attached. Any table that is partitioned is now a RELKIND_PARTITIONED_REL. Thanks, Amit [1] http://www.postgresql.org/message-id/563341AE.5010207@lab.ntt.co.jp
Re: Declarative partitioning
От:
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Дата:
On 2016/04/19 23:52, Amit Langote wrote:
> On Tue, Apr 19, 2016 at 11:26 PM, Alexander Korotkov
>> Another question is that it might be NOT what users expect from that. From
>> the syntax side it very looks like defining something boxes regions for two
>> keys which could be replacement for subpartitioning. But it isn't so.
>
> Need to check why query with qual b < 100 behaves the way it does.
> Something's going wrong there with the constraints (partition
> predicates) that are being generated internally (as mentioned before,
> still driven by constraint exclusion using the constraints generated
> on-the-fly).
>
> As for the composite range partition bounds in Ildar's example, it's
> as if the second value in the key never determines the fate of a row
> going into some partition, therefore no constraints should have been
> generated for column b of the key. I'm afraid that's not the case as
> per the latest patch. Will fix.
The strange behavior that Ildar reported should have been fixed with the
attached updated set of patches (v2):
create table test(a int, b int) partition by range (a, b);
create table test_1 partition of test for values start (0, 0) end (100, 100);
create table test_2 partition of test for values start (100, 100) end
(200, 200);
create table test_3 partition of test for values start (200, 200) end
(300, 300);
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
insert into test(a, b) values (150, 50);
INSERT 0 1
select * from test where b < 100;
a | b
-----+----
150 | 50
(1 row)
explain (costs off) select * from test where b < 100;
QUERY PLAN
---------------------------
Append
-> Seq Scan on test
Filter: (b < 100)
-> Seq Scan on test_1
Filter: (b < 100)
-> Seq Scan on test_2
Filter: (b < 100)
-> Seq Scan on test_3
Filter: (b < 100)
(9 rows)
Multi-column range partitioning seems a bit tricky as far as generating
constraints on individual columns using a partition's lower and upper
bounds (both composite values) is concerned. I mentally pictured
something like the following example scenario:
create table test(a int, b int, c int)
partition by range (a, b, c);
create table test_1 partition of test
for values start (0, 0, 0) end (0, 2, 0);
create table test_2 partition of test
for values start (0, 2, 0) end (0, 3, 0);
create table test_3 partition of test
for values start (0, 3, 0) end (0, 4, 0);
create table test_4 partition of test
for values start (0, 4, 0) end (1, 0, 0);
create table test_5 partition of test
for values start (1, 0, 0) end (1, 2, 0);
create table test_6 partition of test
for values start (1, 2, 0) end (1, 3, 0);
create table test_7 partition of test
for values start (1, 3, 0) end (1, 4, 0);
create table test_8 partition of test
for values start (1, 4, 0) end (2, 0, 0);
Useful to think of the above as sequence of ranges [000, 020), [020, 030),
[030, 040), [040, 100), [100, 120), [120, 130), [130, 140), [140, 200) for
purposes of finding the partition for a row.
Then constraints generated internally for each partition:
test_1: a = 0 AND b >= 0 AND b <= 2
test_2: a = 0 AND b >= 2 AND b <= 3
test_3: a = 0 AND b >= 3 AND b <= 4
test_4: a >= 0 AND a <= 1
test_5: a = 1 AND b >= 0 AND b <= 2
test_6: a = 1 AND b >= 2 AND b <= 3
test_7: a = 1 AND b >= 3 AND b <= 4
test_8: a >= 1 AND a <= 2
I will try further to poke holes in my thinking about this. Please feel
free to point out if you find any.
Thanks,
Amit
Re: Declarative partitioning
От:
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Дата:
Hi Ildar, On 2016/04/21 1:06, Amit Langote wrote: > On Wed, Apr 20, 2016 at 11:46 PM, Ildar Musin wrote: >> Crash occurs in get_check_expr_from_partbound(). It seems that function is >> not yet expecting an expression key and designed to handle only simple >> attributes keys. Backtrace: > > You're right, silly mistake. :-( > > Will fix Attached updated version fixes this. I'll take time to send the next version but I'd very much appreciate it if you keep reporting anything that doesn't look/work right like you did so far. Thanks, Amit
Re: Declarative partitioning
От:
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Дата:
Hi Erik,
On 2016/04/26 17:46, Erik Rijkers wrote:
> On 2016-04-15 04:35, Amit Langote wrote:
>
> A quick test with:
>
>> 0001-Add-syntax-to-specify-partition-key-v3.patch
>> 0002-Infrastructure-for-creation-of-partitioned-tables-v3.patch
>> 0003-Add-syntax-to-create-partitions-v3.patch
>> 0004-Infrastructure-for-partition-metadata-storage-and-ma-v3.patch
>> 0005-Introduce-tuple-routing-for-partitioned-tables-v3.patch
>
> patches apply, build and make check ok.
Thanks for testing!
> There is somwthing wrong with indexes on child tables (and only with
> higher rowcounts).
There was an oversight in patch 0005 that caused partition indexes to not
be opened and tuples inserted into. Attached new version should have
fixed it.
> Surely the below code should give 6 rows; it actually does return 6 rows
> without the indexes.
> With indexes it returns 0 rows.
>
> (but when doing the same test with low rowcounts, things are OK.)
...
> ---------------------------------------
> create table inh(a int, b int) partition by range ((a+b));
> create table inh_1 partition of inh for values start ( 0) end ( 10000);
> create table inh_2 partition of inh for values start ( 10000) end ( 20000);
> create table inh_3 partition of inh for values start ( 20000) end ( 100000);
>
> create index inh_1_a_idx on inh_1 (a);
> create index inh_2_a_idx on inh_2 (a);
> create index inh_3_a_idx on inh_3 (a);
>
> insert into inh select i, i as j from generate_series(1, 10000) as f(i);
>
> analyze inh_1;
> analyze inh_2;
> analyze inh_3;
>
> select 'inh' , count(*) from inh
> union all select 'inh_1', count(*) from inh_1
> union all select 'inh_2', count(*) from inh_2
> union all select 'inh_3', count(*) from inh_3
> ;
>
> explain analyze select * from inh where a between 10110 and 10115;
Hmm, this last query should return 0 rows because:
select max(a) from inh;
max
=------
10000
(1 row)
Did you by any chance mean to write the following:
explain analyze select * from inh where a + b between 10110 and 10115;
In which case:
explain analyze select * from inh where a + b between 10110 and 10115;
QUERY PLAN
=-------------------------------------------------------------------------------------------------------
Append (cost=0.00..123.00 rows=26 width=8) (actual time=0.119..6.407
rows=3 loops=1)
-> Seq Scan on inh (cost=0.00..0.00 rows=1 width=8) (actual
time=0.015..0.015 rows=0 loops=1)
Filter: (((a + b) >= 10110) AND ((a + b) <= 10115))
-> Seq Scan on inh_2 (cost=0.00..123.00 rows=25 width=8) (actual
time=0.076..6.198 rows=3 loops=1)
Filter: (((a + b) >= 10110) AND ((a + b) <= 10115))
Rows Removed by Filter: 4997
Planning time: 0.521 ms
Execution time: 6.572 ms
(8 rows)
select * from inh where a + b between 10110 and 10115;
a | b
=-----+------
5055 | 5055
5056 | 5056
5057 | 5057
(3 rows)
Now that doesn't use index for the obvious reason (mismatched key). So,
let's try one which will:
explain analyze select * from inh where a = 4567;
QUERY PLAN
=------------------------------------------------------------------------------------------------------------------------
Append (cost=0.00..17.61 rows=4 width=8) (actual time=0.189..0.293
rows=1 loops=1)
-> Seq Scan on inh (cost=0.00..0.00 rows=1 width=8) (actual
time=0.016..0.016 rows=0 loops=1)
Filter: (a = 4567)
-> Index Scan using inh_1_a_idx on inh_1 (cost=0.28..8.30 rows=1
width=8) (actual time=0.043..0.056 rows=1 loops=1)
Index Cond: (a = 4567)
-> Index Scan using inh_2_a_idx on inh_2 (cost=0.28..8.30 rows=1
width=8) (actual time=0.024..0.024 rows=0 loops=1)
Index Cond: (a = 4567)
-> Seq Scan on inh_3 (cost=0.00..1.01 rows=1 width=8) (actual
time=0.029..0.029 rows=0 loops=1)
Filter: (a = 4567)
Rows Removed by Filter: 1
Planning time: 0.589 ms
Execution time: 0.433 ms
select * from inh where a = 4567;
a | b
=-----+------
4567 | 4567
(1 row)
No pruning occurs this time for the obvious reason (mismatched key).
Does that help clarify?
Thanks,
Amit
Re: Declarative partitioning
От:
Ildar Musin <i.musin@postgrespro.ru>
Дата:
Hi Amit and all,
Here is an experimental patch that optimizes planning time for range partitioned tables (it could be considered as a "proof of concept"). Patch should be applied on top of Amit's declarative partitioning patch. It handles only a very special case (often used though) where partitioning key consists of just a single attribute and doesn't contain expressions.
The main idea is the following:
* we are looking for clauses like 'VAR OP CONST' (where VAR is partitioning key attribute, OP is a comparison operator);
* using binary search find a partition (X) that fits CONST value;
* based on OP operator determine which partitions are also covered by clause. There are possible cases:
1. If OP is '<' or '<=' then we need partitions standing left from X (including)
2. If OP is '>' or '>=' then we need partitions standing right from X (including)
3. If OP is '=' the we need only X partition
(for '<' and '>' operators we also check if CONST value is equal to a lower or upper boundary (accordingly) and if it's true then exclude X).
For boolean expressions we evaluate left and right sides accordingly to algorithm above and then based on boolean operator find intersection (for AND) or union (for OR).
I run some benchmarks on:
1. original constraint exclusion mechanism,
2. optimized version (this patch) and
3. optimized version using relation->rd_partdesc pointer instead of RelationGetPartitionDesc() function (see previous discussion).
Initial conditions:
CREATE TABLE abc (id SERIAL NOT NULL, a INT, dt TIMESTAMP) PARTITION BY RANGE (a);
CREATE TABLE abc_1 PARTITION OF abc FOR VALUES START (0) END (1000);
CREATE TABLE abc_2 PARTITION OF abc FOR VALUES START (1000) END (2000);
...
etc
INSERT INTO %s (a) SELECT generate_series(0, <partitions_count> * 1000);
pgbench scripts: https://gist.github.com/zilder/872e634a8eeb405bd045465fc9527e53 (where :partitions is a number of partitions).
The first script tests fetching a single row from the partitioned table. Results (tps):
# of partitions | constraint excl. | optimized | optimized (using pointer)
----------------+------------------+---------------+----------------------------
100 | 658 | 2906 | 3079
1000 | 45 | 2174 | 3021
2000 | 22 | 1667 | 2919
The second script tests fetching all data from a single partition. Results (tps):
# of partitions | constraint excl. | optimized | optimized (using pointer)
----------------+------------------+---------------+----------------------------
100 | 317 | 1001 | 1051
1000 | 34 | 941 | 1023
2000 | 15 | 813 | 1016
Optimized version works much faster on large amount of partitions and degradates slower than constraint exclusion. But still there is a noticeable performance degradation from copying PartitionDesc structure: with 2000 partitions RelationGetPartitionDesc() function spent more than 40% of all execution time on copying in first benchmark (measured with `perf`). Using reference counting as Amit suggests will allow to significantily decrease performance degradation.
Any comments and suggestions are very welcome. Thanks!
On 18.05.2016 04:26, Amit Langote wrote:
On 2016/05/18 2:22, Tom Lane wrote:The two ways that we've dealt with this type of hazard are to copy data out of the relcache before using it; or to give the relcache the responsibility of not moving a particular portion of data if it did not change. From memory, the latter applies to the tuple descriptor and trigger data, but we've done most other things the first way.It seems that tuple descriptor is reference-counted; however trigger data is copied. The former seems to have been done on performance grounds (I found 06e10abc). So for a performance-sensitive relcache data structure, refcounting is the way to go (although done quite rarely)? In this particular case, it is a "partition descriptor" that could get big for a partitioned table with partitions in hundreds or thousands. Thanks, Amit
Here is an experimental patch that optimizes planning time for range partitioned tables (it could be considered as a "proof of concept"). Patch should be applied on top of Amit's declarative partitioning patch. It handles only a very special case (often used though) where partitioning key consists of just a single attribute and doesn't contain expressions.
The main idea is the following:
* we are looking for clauses like 'VAR OP CONST' (where VAR is partitioning key attribute, OP is a comparison operator);
* using binary search find a partition (X) that fits CONST value;
* based on OP operator determine which partitions are also covered by clause. There are possible cases:
1. If OP is '<' or '<=' then we need partitions standing left from X (including)
2. If OP is '>' or '>=' then we need partitions standing right from X (including)
3. If OP is '=' the we need only X partition
(for '<' and '>' operators we also check if CONST value is equal to a lower or upper boundary (accordingly) and if it's true then exclude X).
For boolean expressions we evaluate left and right sides accordingly to algorithm above and then based on boolean operator find intersection (for AND) or union (for OR).
I run some benchmarks on:
1. original constraint exclusion mechanism,
2. optimized version (this patch) and
3. optimized version using relation->rd_partdesc pointer instead of RelationGetPartitionDesc() function (see previous discussion).
Initial conditions:
CREATE TABLE abc (id SERIAL NOT NULL, a INT, dt TIMESTAMP) PARTITION BY RANGE (a);
CREATE TABLE abc_1 PARTITION OF abc FOR VALUES START (0) END (1000);
CREATE TABLE abc_2 PARTITION OF abc FOR VALUES START (1000) END (2000);
...
etc
INSERT INTO %s (a) SELECT generate_series(0, <partitions_count> * 1000);
pgbench scripts: https://gist.github.com/zilder/872e634a8eeb405bd045465fc9527e53 (where :partitions is a number of partitions).
The first script tests fetching a single row from the partitioned table. Results (tps):
# of partitions | constraint excl. | optimized | optimized (using pointer)
----------------+------------------+---------------+----------------------------
100 | 658 | 2906 | 3079
1000 | 45 | 2174 | 3021
2000 | 22 | 1667 | 2919
The second script tests fetching all data from a single partition. Results (tps):
# of partitions | constraint excl. | optimized | optimized (using pointer)
----------------+------------------+---------------+----------------------------
100 | 317 | 1001 | 1051
1000 | 34 | 941 | 1023
2000 | 15 | 813 | 1016
Optimized version works much faster on large amount of partitions and degradates slower than constraint exclusion. But still there is a noticeable performance degradation from copying PartitionDesc structure: with 2000 partitions RelationGetPartitionDesc() function spent more than 40% of all execution time on copying in first benchmark (measured with `perf`). Using reference counting as Amit suggests will allow to significantily decrease performance degradation.
Any comments and suggestions are very welcome. Thanks!
-- Ildar Musin i.musin@postgrespro.ru
Re: Declarative partitioning
От:
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Дата:
Hi Ildar, On 2016/05/19 0:36, Ildar Musin wrote: > > Here is an experimental patch that optimizes planning time for range > partitioned tables (it could be considered as a "proof of concept"). Patch > should be applied on top of Amit's declarative partitioning patch. It > handles only a very special case (often used though) where partitioning > key consists of just a single attribute and doesn't contain expressions. Great, thanks! I understand that it's still PoC and the point may be just to consider performance implications of excessive partdesc copying but I'm wondering about a few things about the patch in general. See below. > The main idea is the following: > * we are looking for clauses like 'VAR OP CONST' (where VAR is > partitioning key attribute, OP is a comparison operator); > * using binary search find a partition (X) that fits CONST value; > * based on OP operator determine which partitions are also covered by > clause. There are possible cases: > 1. If OP is '<' or '<=' then we need partitions standing left from X > (including) > 2. If OP is '>' or '>=' then we need partitions standing right from X > (including) > 3. If OP is '=' the we need only X partition > (for '<' and '>' operators we also check if CONST value is equal to a > lower or upper boundary (accordingly) and if it's true then exclude X). > > For boolean expressions we evaluate left and right sides accordingly to > algorithm above and then based on boolean operator find intersection (for > AND) or union (for OR). Perhaps you're already aware but may I also suggest looking at how clauses are matched to indexes? For example, consider how match_clauses_to_index() in src/backend/optimizer/path/indxpath.c works. Moreover, instead of pruning partitions in planner prep phase, might it not be better to do that when considering paths for the (partitioned) rel? IOW, instead of looking at parse->jointree, we should rather be working with rel->baserestrictinfo. Although, that would require some revisions to how append_rel_list, simple_rel_list, etc. are constructed and manipulated in a given planner invocation. Maybe it's time for that... Again, you may have already considered these things. > I run some benchmarks on: > 1. original constraint exclusion mechanism, > 2. optimized version (this patch) and > 3. optimized version using relation->rd_partdesc pointer instead of > RelationGetPartitionDesc() function (see previous discussion). > > Initial conditions: > > CREATE TABLE abc (id SERIAL NOT NULL, a INT, dt TIMESTAMP) PARTITION BY > RANGE (a); > CREATE TABLE abc_1 PARTITION OF abc FOR VALUES START (0) END (1000); > CREATE TABLE abc_2 PARTITION OF abc FOR VALUES START (1000) END (2000); > ... > etc > INSERT INTO %s (a) SELECT generate_series(0, * 1000); > > pgbench scripts: > https://gist.github.com/zilder/872e634a8eeb405bd045465fc9527e53 (where > :partitions is a number of partitions). > The first script tests fetching a single row from the partitioned table. > Results (tps): > > # of partitions | constraint excl. | optimized | optimized (using > pointer) > ----------------+------------------+---------------+---------------------------- > > 100 | 658 | 2906 | 3079 > 1000 | 45 | 2174 | 3021 > 2000 | 22 | 1667 | 2919 > > > The second script tests fetching all data from a single partition. Results > (tps): > > # of partitions | constraint excl. | optimized | optimized (using > pointer) > ----------------+------------------+---------------+---------------------------- > > 100 | 317 | 1001 | 1051 > 1000 | 34 | 941 | 1023 > 2000 | 15 | 813 | 1016 > > Optimized version works much faster on large amount of partitions and > degradates slower than constraint exclusion. But still there is a > noticeable performance degradation from copying PartitionDesc structure: > with 2000 partitions RelationGetPartitionDesc() function spent more than > 40% of all execution time on copying in first benchmark (measured with > `perf`). Using reference counting as Amit suggests will allow to > significantily decrease performance degradation. Could you try with the attached updated set of patches? I changed partition descriptor relcache code to eliminate excessive copying in previous versions. Thanks, Amit
Re: Declarative partitioning
От:
Ildar Musin <i.musin@postgrespro.ru>
Дата:
Hi Amit,
# of partitions | single row | single partition
----------------+------------+------------------
100 | 3014 | 1024
1000 | 2964 | 1001
2000 | 2874 | 1000
However I've encountered a problem which is that postgres crashes occasionally while creating partitions. Here is function that reproduces this behaviour:
CREATE OR REPLACE FUNCTION fail()
RETURNS void
LANGUAGE plpgsql
AS $$
BEGIN
DROP TABLE IF EXISTS abc CASCADE;
CREATE TABLE abc (id SERIAL NOT NULL, a INT, dt TIMESTAMP) PARTITION BY RANGE (a);
CREATE INDEX ON abc (a);
CREATE TABLE abc_0 PARTITION OF abc FOR VALUES START (0) END (1000);
CREATE TABLE abc_1 PARTITION OF abc FOR VALUES START (1000) END (2000);
END
$$;
SELECT fail();
It happens not every time but quite often. It doesn't happen if I execute this commands one by one in psql. Backtrace:
#0 range_overlaps_existing_partition (key=0x7f1097504410, range_spec=0x1d0f400, pdesc=0x1d32200, with=0x7ffe437ead00) at partition.c:747
#1 0x000000000054c2a5 in StorePartitionBound (relid=245775, parentId=245770, bound=0x1d0f400) at partition.c:578
#2 0x000000000061bfc4 in DefineRelation (stmt=0x1d0dfe0, relkind=114 'r', ownerId=10, typaddress=0x0) at tablecmds.c:739
#3 0x00000000007f4473 in ProcessUtilitySlow (parsetree=0x1d1a150, queryString=0x1d1d940 "CREATE TABLE abc_0 PARTITION OF abc FOR VALUES START (0) END (1000)", context=PROCESS_UTILITY_QUERY, params=0x0, dest=0xdb5ca0 <spi_printtupDR>, completionTag=0x7ffe437eb500 "")
at utility.c:983
#4 0x00000000007f425e in standard_ProcessUtility (parsetree=0x1d1a150, queryString=0x1d1d940 "CREATE TABLE abc_0 PARTITION OF abc FOR VALUES START (0) END (1000)", context=PROCESS_UTILITY_QUERY, params=0x0, dest=0xdb5ca0 <spi_printtupDR>,
completionTag=0x7ffe437eb500 "") at utility.c:907
#5 0x00000000007f3354 in ProcessUtility (parsetree=0x1d1a150, queryString=0x1d1d940 "CREATE TABLE abc_0 PARTITION OF abc FOR VALUES START (0) END (1000)", context=PROCESS_UTILITY_QUERY, params=0x0, dest=0xdb5ca0 <spi_printtupDR>, completionTag=0x7ffe437eb500 "")
at utility.c:336
#6 0x000000000069f8b2 in _SPI_execute_plan (plan=0x1d19cf0, paramLI=0x0, snapshot=0x0, crosscheck_snapshot=0x0, read_only=0 '\000', fire_triggers=1 '\001', tcount=0) at spi.c:2200
#7 0x000000000069c735 in SPI_execute_plan_with_paramlist (plan=0x1d19cf0, params=0x0, read_only=0 '\000', tcount=0) at spi.c:450
#8 0x00007f108cc6266f in exec_stmt_execsql (estate=0x7ffe437eb8e0, stmt=0x1d05318) at pl_exec.c:3517
#9 0x00007f108cc5e5fc in exec_stmt (estate=0x7ffe437eb8e0, stmt=0x1d05318) at pl_exec.c:1503
#10 0x00007f108cc5e318 in exec_stmts (estate=0x7ffe437eb8e0, stmts=0x1d04c98) at pl_exec.c:1398
#11 0x00007f108cc5e1af in exec_stmt_block (estate=0x7ffe437eb8e0, block=0x1d055e0) at pl_exec.c:1336
#12 0x00007f108cc5c35d in plpgsql_exec_function (func=0x1cc2a90, fcinfo=0x1cf7f50, simple_eval_estate=0x0) at pl_exec.c:434
...
Thanks
On 20.05.2016 11:37, Amit Langote wrote:
Thanks, I'll take a closer look at it.Perhaps you're already aware but may I also suggest looking at how clauses are matched to indexes? For example, consider how match_clauses_to_index() in src/backend/optimizer/path/indxpath.c works.
Yes, you're right, this is how we did it in pg_pathman extension. But for this patch it requires further consideration and I'll do it in future!Moreover, instead of pruning partitions in planner prep phase, might it not be better to do that when considering paths for the (partitioned) rel?IOW, instead of looking at parse->jointree, we should rather be working with rel->baserestrictinfo. Although, that would require some revisions to how append_rel_list, simple_rel_list, etc. are constructed and manipulated in a given planner invocation. Maybe it's time for that... Again, you may have already considered these things.
I tried your new patch and got following results, which are quite close to the ones using pointer to PartitionDesc structure (TPS):Could you try with the attached updated set of patches? I changed partition descriptor relcache code to eliminate excessive copying in previous versions. Thanks, Amit
# of partitions | single row | single partition
----------------+------------+------------------
100 | 3014 | 1024
1000 | 2964 | 1001
2000 | 2874 | 1000
However I've encountered a problem which is that postgres crashes occasionally while creating partitions. Here is function that reproduces this behaviour:
CREATE OR REPLACE FUNCTION fail()
RETURNS void
LANGUAGE plpgsql
AS $$
BEGIN
DROP TABLE IF EXISTS abc CASCADE;
CREATE TABLE abc (id SERIAL NOT NULL, a INT, dt TIMESTAMP) PARTITION BY RANGE (a);
CREATE INDEX ON abc (a);
CREATE TABLE abc_0 PARTITION OF abc FOR VALUES START (0) END (1000);
CREATE TABLE abc_1 PARTITION OF abc FOR VALUES START (1000) END (2000);
END
$$;
SELECT fail();
It happens not every time but quite often. It doesn't happen if I execute this commands one by one in psql. Backtrace:
#0 range_overlaps_existing_partition (key=0x7f1097504410, range_spec=0x1d0f400, pdesc=0x1d32200, with=0x7ffe437ead00) at partition.c:747
#1 0x000000000054c2a5 in StorePartitionBound (relid=245775, parentId=245770, bound=0x1d0f400) at partition.c:578
#2 0x000000000061bfc4 in DefineRelation (stmt=0x1d0dfe0, relkind=114 'r', ownerId=10, typaddress=0x0) at tablecmds.c:739
#3 0x00000000007f4473 in ProcessUtilitySlow (parsetree=0x1d1a150, queryString=0x1d1d940 "CREATE TABLE abc_0 PARTITION OF abc FOR VALUES START (0) END (1000)", context=PROCESS_UTILITY_QUERY, params=0x0, dest=0xdb5ca0 <spi_printtupDR>, completionTag=0x7ffe437eb500 "")
at utility.c:983
#4 0x00000000007f425e in standard_ProcessUtility (parsetree=0x1d1a150, queryString=0x1d1d940 "CREATE TABLE abc_0 PARTITION OF abc FOR VALUES START (0) END (1000)", context=PROCESS_UTILITY_QUERY, params=0x0, dest=0xdb5ca0 <spi_printtupDR>,
completionTag=0x7ffe437eb500 "") at utility.c:907
#5 0x00000000007f3354 in ProcessUtility (parsetree=0x1d1a150, queryString=0x1d1d940 "CREATE TABLE abc_0 PARTITION OF abc FOR VALUES START (0) END (1000)", context=PROCESS_UTILITY_QUERY, params=0x0, dest=0xdb5ca0 <spi_printtupDR>, completionTag=0x7ffe437eb500 "")
at utility.c:336
#6 0x000000000069f8b2 in _SPI_execute_plan (plan=0x1d19cf0, paramLI=0x0, snapshot=0x0, crosscheck_snapshot=0x0, read_only=0 '\000', fire_triggers=1 '\001', tcount=0) at spi.c:2200
#7 0x000000000069c735 in SPI_execute_plan_with_paramlist (plan=0x1d19cf0, params=0x0, read_only=0 '\000', tcount=0) at spi.c:450
#8 0x00007f108cc6266f in exec_stmt_execsql (estate=0x7ffe437eb8e0, stmt=0x1d05318) at pl_exec.c:3517
#9 0x00007f108cc5e5fc in exec_stmt (estate=0x7ffe437eb8e0, stmt=0x1d05318) at pl_exec.c:1503
#10 0x00007f108cc5e318 in exec_stmts (estate=0x7ffe437eb8e0, stmts=0x1d04c98) at pl_exec.c:1398
#11 0x00007f108cc5e1af in exec_stmt_block (estate=0x7ffe437eb8e0, block=0x1d055e0) at pl_exec.c:1336
#12 0x00007f108cc5c35d in plpgsql_exec_function (func=0x1cc2a90, fcinfo=0x1cf7f50, simple_eval_estate=0x0) at pl_exec.c:434
...
Thanks
-- Ildar Musin i.musin@postgrespro.ru
Re: Declarative partitioning
От:
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Дата:
Hi Ildar, On 2016/05/21 0:29, Ildar Musin wrote: > On 20.05.2016 11:37, Amit Langote wrote: >> Moreover, instead of pruning partitions in planner prep phase, might it >> not be better to do that when considering paths for the (partitioned) rel? >> IOW, instead of looking at parse->jointree, we should rather be working >> with rel->baserestrictinfo. Although, that would require some revisions >> to how append_rel_list, simple_rel_list, etc. are constructed and >> manipulated in a given planner invocation. Maybe it's time for that... >> Again, you may have already considered these things. >> > Yes, you're right, this is how we did it in pg_pathman extension. But for > this patch it requires further consideration and I'll do it in future! OK, sure. >> Could you try with the attached updated set of patches? I changed >> partition descriptor relcache code to eliminate excessive copying in >> previous versions. [...] > However I've encountered a problem which is that postgres crashes > occasionally while creating partitions. Here is function that reproduces > this behaviour: > > CREATE OR REPLACE FUNCTION fail() > RETURNS void > LANGUAGE plpgsql > AS $$ > BEGIN > DROP TABLE IF EXISTS abc CASCADE; > CREATE TABLE abc (id SERIAL NOT NULL, a INT, dt TIMESTAMP) PARTITION BY > RANGE (a); > CREATE INDEX ON abc (a); > CREATE TABLE abc_0 PARTITION OF abc FOR VALUES START (0) END (1000); > CREATE TABLE abc_1 PARTITION OF abc FOR VALUES START (1000) END (2000); > END > $$; > > SELECT fail(); > > It happens not every time but quite often. It doesn't happen if I execute > this commands one by one in psql. Backtrace: > > #0 range_overlaps_existing_partition (key=0x7f1097504410, > range_spec=0x1d0f400, pdesc=0x1d32200, with=0x7ffe437ead00) at > partition.c:747 [...] I made a mistake in the last version of the patch which caused a relcache field to be pfree'd unexpectedly. Attached updated patches. Thanks, Amit
Re: Declarative partitioning
От:
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Дата:
Hi Ashutosh,
On 2016/06/24 23:08, Ashutosh Bapat wrote:
> Hi Amit,
> I tried creating 2-level partitioned table and tried to create simple table
> using CTAS from the partitioned table. It gives a cache lookup error.
> Here's the test
> CREATE TABLE pt1_l (a int, b varchar, c int) PARTITION BY RANGE(a);
> CREATE TABLE pt1_l_p1 PARTITION OF pt1_l FOR VALUES START (1) END (250)
> INCLUSIVE PARTITION BY RANGE(b);
> CREATE TABLE pt1_l_p2 PARTITION OF pt1_l FOR VALUES START (251) END (500)
> INCLUSIVE PARTITION BY RANGE(((a+c)/2));
> CREATE TABLE pt1_l_p3 PARTITION OF pt1_l FOR VALUES START (501) END (600)
> INCLUSIVE PARTITION BY RANGE(c);
> CREATE TABLE pt1_l_p1_p1 PARTITION OF pt1_l_p1 FOR VALUES START ('000001')
> END ('000125') INCLUSIVE;
> CREATE TABLE pt1_l_p1_p2 PARTITION OF pt1_l_p1 FOR VALUES START ('000126')
> END ('000250') INCLUSIVE;
> CREATE TABLE pt1_l_p2_p1 PARTITION OF pt1_l_p2 FOR VALUES START (251) END
> (375) INCLUSIVE;
> CREATE TABLE pt1_l_p2_p2 PARTITION OF pt1_l_p2 FOR VALUES START (376) END
> (500) INCLUSIVE;
> CREATE TABLE pt1_l_p3_p1 PARTITION OF pt1_l_p3 FOR VALUES START (501) END
> (550) INCLUSIVE;
> CREATE TABLE pt1_l_p3_p2 PARTITION OF pt1_l_p3 FOR VALUES START (551) END
> (600) INCLUSIVE;
> INSERT INTO pt1_l SELECT i, to_char(i, 'FM000000'), i FROM
> generate_series(1, 600, 2) i;
> CREATE TABLE upt1_l AS SELECT * FROM pt1_l;
>
> The last statement gives error "ERROR: cache lookup failed for function
> 0". Let me know if this problem is reproducible.
Thanks for the test case. I can reproduce the error. It has to do with
partition key column (b) being of type varchar whereas the default
operator family for the type being text_ops and there not being an
=(varchar, varchar) operator in text_ops.
When creating a plan for SELECT * FROM pt1_l, which is an Append plan,
partition check quals are generated internally to be used for constraint
exclusion - such as, b >= '000001' AND b < '000125'. Individual OpExpr's
are generated by using information from PartitionKey of the parent
(PartitionKey) and pg_partition entry of the partition. When choosing the
operator to use for =, >=, <, etc., opfamily and typid of corresponding
columns are referred. As mentioned above, in this case, they happened to
be text_ops and varchar, respectively, for column b. There doesn't exist
an operator =(varchar, varchar) in text_ops, so InvalidOid is returned by
get_opfamily_member which goes unchecked until the error in question occurs.
I have tried to fix this in attached updated patch by using operator class
input type for operator resolution in cases where column type didn't help.
Thanks,
Amit
Re: Declarative partitioning
От:
Amit Langote <amitlangote09@gmail.com>
Дата:
On Wednesday, 21 October 2015, Thom Brown <thom@linux.com> wrote:
On 18 August 2015 at 12:23, Amit Langote <amitlangote09@gmail.com> wrote:
> Hi Thom,
>
> On Tue, Aug 18, 2015 at 8:02 PM, Thom Brown <thom@linux.com> wrote:
>>
>>
>> Wow, didn't expect to see that email this morning.
>>
>> A very quick test:
>>
>> CREATE TABLE purchases (purchase_id serial, purchase_time timestamp, item
>> text) partition by range on ((extract(year from
>> purchase_time)),(extract(month from purchase_time)));
>> ERROR: referenced relation "purchases" is not a table or foreign table
>>
>
> Thanks for the quick test.
>
> Damn, I somehow missed adding the new relkind to a check in
> process_owned_by(). Will fix this and look for any such oversights.
This doesn't seem to have materialised. Are you still working on this?
Yes, I will be posting to this thread soon. Sorry about the silence.
Thanks,
Amit
Re: Declarative partitioning
От:
Thom Brown <thom@linux.com>
Дата:
On 18 August 2015 at 11:30, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
Hi,
I would like propose $SUBJECT for this development cycle. Attached is a
WIP patch that implements most if not all of what's described below. Some
yet unaddressed parts are mentioned below, too. I'll add this to the CF-SEP.
Syntax
======
1. Creating a partitioned table
CREATE TABLE table_name
PARTITION BY {RANGE|LIST}
ON (column_list);
Where column_list consists of simple column names or expressions:
PARTITION BY LIST ON (name)
PARTITION BY RANGE ON (year, month)
PARTITION BY LIST ON ((lower(left(name, 2)))
PARTITION BY RANGE ON ((extract(year from d)), (extract(month from d)))
Note: LIST partition key supports only one column.
For each column, you could write operator class name:
PARTITION BY LIST/RANGE ON (colname [USING] opclass_name),
If not specified, the default btree operator class based on type of each
key column is used. If none of the available btree operator classes are
compatible with the partitioning strategy (list/range), error is thrown.
Built-in btree operator classes cover a good number of types for list and
range partitioning in practical scenarios.
A table created using this form is of proposed new relkind
RELKIND_PARTITIONED_REL. An entry in pg_partitioned_rel (see below) is
created to store partition key info.
Note: A table cannot be partitioned after-the-fact using ALTER TABLE.
Normal dependencies are created between the partitioned table and operator
classes, object in partition expressions like functions.
2. Creating a partition of a partitioned table
CREATE TABLE table_name
PARTITION OF partitioned_table_name
FOR VALUES values_spec;
Where values_spec is:
listvalues: [IN] (val1, ...)
rangevalues: START (col1min, ... ) END (col1max, ... )
| START (col1min, ... )
| END (col1max, ... )
A table created using this form has proposed pg_class.relispartition set
to true. An entry in pg_partition (see below) is created to store the
partition bound info.
The values_spec should match the partitioning strategy of the partitioned
table. In case of a range partition, the values in START and/or END should
match columns in the partition key.
Defining a list partition is fairly straightforward - just spell out the
list of comma-separated values. Error is thrown if the list of values
overlaps with one of the existing partitions' list.
CREATE TABLE persons_by_state (name text, state text)
PARTITION BY LIST ON (state);
CREATE TABLE persons_IL
PARTITION OF persons_by_state
FOR VALUES IN ('IL');
CREATE TABLE persons_fail
PARTITION OF persons_by_state
FOR VALUES IN ('IL');
ERROR: cannot create partition that overlaps with an existing one
For a range partition, there are more than one way:
Specify both START and END bounds: resulting range should not overlap with
the range(s) covered by existing partitions. Error is thrown otherwise.
Although rare in practice, gaps between ranges are OK.
CREATE TABLE measurement(logdate date NOT NULL)
PARTITION BY RANGE ON (logdate);
CREATE TABLE measurement_y2006m02
PARTITION OF measurement
FOR VALUES START ('2006-02-01') END ('2006-03-01'); --success
CREATE TABLE measurement_fail
PARTITION OF measurement
FOR VALUES START ('2006-02-15') END ('2006-03-01');
ERROR: cannot create partition that overlaps with an existing one
Specify only the START bound: add the partition on the left of some range
covered by existing partitions provided no overlap occurs (also
considering gaps between ranges, if any). If no such range exists, the new
partition will cover the range [START, +INFINITY) and become the rightmost
partition. Error is thrown if the specified START causes overlap.
CREATE TABLE measurement_y2006m01
PARTITION OF measurement
FOR VALUES START ('2006-01-01'); --success
CREATE TABLE measurement_fail
PARTITION OF measurement
FOR VALUES START ('2006-02-01'); --overlaps with measurement_y2006m02
ERROR: cannot create partition that overlaps with an existing one
Specify only the END bound: add the partition on the right of some range
covered by existing partitions provided no overlap occurs (also
considering gaps between ranges, if any). If no such range exists, the new
partition would cover the range (-INFINITY, END) and become the leftmost
partition. Error is thrown if the specified END causes overlap.
CREATE TABLE measurement_y2006m03
PARTITION OF measurement
FOR VALUES END ('2006-04-01'); --success
CREATE TABLE measurement_fail
PARTITION OF measurement
FOR VALUES END ('2006-03-01'); --overlaps with measurement_y2006m02
ERROR: cannot create partition that overlaps with an existing one
For each partition, START and END bound values are stored in the
catalog. Note that the lower bound is inclusive, whereas the upper bound
is exclusive.
Note: At most one range partition can have null min bound in which case it
covers the range (-INFINITY, END). Also, at most one range partition can
have null max bound in which case it covers the range [START, +INFINITY).
A normal dependency is created between the parent and the partition.
3. Multi-level partitioning
CREATE TABLE table_name
PARTITION OF partitioned_table_name
FOR VALUES values_spec
PARTITION BY {RANGE|LIST} ON (columns_list)
This variant implements a form of so called composite or sub-partitioning
with arbitrarily deep partitioning structure. A table created using this
form has both the relkind RELKIND_PARTITIONED_REL and
pg_class.relispartition set to true.
4. (yet unimplemented) Attach partition (from existing table)
ALTER TABLE partitioned_table
ATTACH PARTITION partition_name
FOR VALUES values_spec
USING [TABLE] table_name;
ALTER TABLE table_name
SET VALID PARTITION OF <parent>;
The first of the above pair of commands would attach table_name as a (yet)
'invalid' partition of partitioned_table (after confirming that it matches
the schema and does not overlap with other partitions per FOR VALUES
spec). It would also record the FOR VALUES part in the partition catalog
and set pg_class.relispartition to true for table_name.
After the first command is done, the second command would take exclusive
lock on table_name, scan the table to check if it contains any values
outside the boundaries defined by FOR VALUES clause defined previously,
throw error if so, mark as valid partition of parent if not.
Does that make sense?
5. Detach partition
ALTER TABLE partitioned_table
DETACH PARTITION partition_name [USING table_name]
This removes partition_name as partition of partitioned_table. The table
continues to exist with the same name or 'table_name', if specified.
pg_class.relispartition is set to false for the table, so it behaves like
a normal table.
System catalogs
===============
1. pg_partitioned_rel
CATALOG(pg_partitioned_rel,oid) BKI_WITHOUT_OIDS
{
Oid partrelid; /* partitioned table pg_class.oid */
char partstrategy; /* partitioning strategy 'l'/'r' */
int16 partnatts; /* number of partition columns */
int2vector partkey; /* column numbers of partition columns;
* 0 where specified column is an
* expresion */
oidvector partclass; /* operator class to compare keys */
pg_node_tree partexprs; /* expression trees for partition key
* members that are not simple column
* references; one for each zero entry
* in partkey[] */
};
2. pg_partition (omits partisvalid alluded to above)
CATALOG(pg_partition,oid) BKI_WITHOUT_OIDS
{
Oid partitionid; /* partition oid */
Oid partparent; /* parent oid */
anyarray partlistvalues; /* list of allowed values of the only
* partition column */
anyarray partrangebounds; /* list of bounds of ranges of
* allowed values per partition key
* column */
};
Further notes
=============
There are a number of restrictions on performing after-the-fact changes
using ALTER TABLE to partitions (ie, relispartition=true):
* Cannot add/drop column
* Cannot set/drop OIDs
* Cannot set/drop NOT NULL
* Cannot set/drop default
* Cannot alter column type
* Cannot add/drop alter constraint (table level)
* Cannot change persistence
* Cannot change inheritance
* Cannot link to a composite type
Such changes should be made to the topmost parent in the partitioning
hierarchy (hereafter referred to as just parent). These are recursively
applied to all the tables in the hierarchy. Although the last two items
cannot be performed on parent either.
Dropping a partition using DROP TABLE is not allowed. It needs to detached
using ALTER TABLE on parent before it can be dropped as a normal table.
Triggers on partitions are not allowed. They should be defined on the
parent. That said, I could not figure out a way to implement row-level
AFTER triggers on partitioned tables (more about that in a moment); so
they are currently not allowed:
CREATE TRIGGER audit_trig
AFTER INSERT ON persons
FOR EACH ROW EXECUTE PROCEDURE audit_func();
ERROR: Row-level AFTER triggers are not supported on partitioned tables
Column/table constraints on partitions are not allowed. They should be
defined on the parent. Foreign key constraints are not allowed due to
above limitation (no row-level after triggers).
A partitioning parent (RELKIND_PARTITIONED_REL) does not have storage.
Creating index on parent is not allowed. They should be defined on (leaf)
partitions. Because of this limitation, primary keys are not allowed on a
partitioned table. Perhaps, we should be able to just create a dummy
entry somewhere to represent an index on parent (which every partition
then copies.) Then by restricting primary key to contain all partition key
columns, we can implement unique constraint over the whole partitioned
table. That will in turn allow us to use partitioned tables as PK rels in
a foreign key constraint provided row-level AFTER trigger issue is resolved.
VACUUM/ANALYZE on individual partitions should work like normal tables.
I've not implemented something like inheritance tree sampling for
partitioning tree in this patch. Autovacuum has been taught to ignore
parent tables and vacuum/analyze partitions normally.
Dropping a partitioned table should (?) unconditionally drop all its
partitions probably but, currently the patch uses dependencies, so
requires to specify CASCADE to do the same.
What should TRUNCATE on partitioned table do?
Ownership, privileges/permissions, RLS should be managed through the
parent table although not comprehensively addressed in the patch.
There is no need to define tuple routing triggers. CopyFrom() and
ExecInsert() determine target partition just before performing
heap_insert() and ExecInsertIndexTuples(). IOW, any BR triggers and
constraints (on parent) are executed for tuple before being routed to a
partition. If no partition can be found, it's an error.
Because row-level AFTER triggers need to save ItemPointers in trigger
event data and defining triggers on partitions (which is where tuples
really go) is not allowed, I could not find a straightforward way to
implement them. So, perhaps we should allow (only) row-level AFTER
triggers on partitions or think of modifying trigger.c to know about this
twist explicitly.
Internal representations
========================
For a RELKIND_PARTITIONED_REL relations, RelationData gets a few new
fields to store the partitioning metadata. That includes partition key
tuple (pg_partitioned_rel) including some derived info (opfamily,
opcintype, compare proc FmgrInfos, partition expression trees).
Additionally, it also includes a PartitionInfo object which includes
partition OIDs array, partition bound arrays (if range partitioned,
rangemax is sorted in ascending order and OIDs are likewise ordered). It
is built from information in pg_partition catalog.
While RelationBuildDesc() initializes the basic key info, fields like
expression trees, PartitionInfo are built on demand and cached. For
example, InitResultRelInfo() builds the latter to populate the newly added
ri_PartitionKeyInfo and ri_Partitions fields, respectively.
PartitionInfo object is rebuilt on every cache invalidation of the rel
which includes when adding/attaching/detaching a new partition.
Planner and executor considerations
=====================================
The patch does not yet implement any planner changes for partitioned
tables, although I'm working on the same and post updates as soon as
possible. That means, it is not possible to run SELECT/UPDATE/DELETE
queries on partitioned tables without getting:
postgres=# SELECT * FROM persons;
ERROR: could not open file "base/13244/106975": No such file or directory
Given that there would be more direct ways of performing partition pruning
decisions with the proposed, it would be nice to utilize them.
Specifically, I would like to avoid having to rely on constraint exclusion
for partition pruning whereby subquery_planner() builds append_rel_list
and the later steps exclude useless partitions.
By extending RelOptInfo to include partitioning info for partitioned rels,
it might be possible to perform partition pruning directly without
previously having to expand them. Although, as things stand now, it's not
clear how that might work - when would partition RTEs be added to the
rtable? The rtable is assumed not to change after
setup_simple_rel_arrays() has done its job which is much earlier than when
it would be desirable for the partitioned table expansion (along with
partition pruning) to happen. Moreover, if that means we might not be able
to build RelOptInfo's for partitions, how to choose best paths for them
(index paths or not, etc.)?
I'm also hoping we don't require something like inheritance_planner() for
when partitioned tables are target rels. I assume considerations for why
the special processing is necessary for inheritance trees in that scenario
don't apply to partitioning trees. So, if grouping_planner() returns a
Append plan (among other options) for the partitioning tree, tacking a
ModifyTable node on top should do the trick?
Suggestions greatly welcome in this area.
Other items
===========
Will include the following once we start reaching consensus on main parts
of the proposed design/implementation:
* New regression tests
* Documentation updates
* pg_dump, psql, etc.
For reference, some immediately previous discussions:
* On partitioning *
http://www.postgresql.org/message-id/20140829155607.GF7705@eldon.alvh.no-ip.org
* Partitioning WIP patch *
http://www.postgresql.org/message-id/54EC32B6.9070605@lab.ntt.co.jp
Comments welcome!
Thanks,
Amit
Wow, didn't expect to see that email this morning.
A very quick test:
CREATE TABLE purchases (purchase_id serial, purchase_time timestamp, item text) partition by range on ((extract(year from purchase_time)),(extract(month from purchase_time)));
ERROR: referenced relation "purchases" is not a table or foreign table
A very quick test:
CREATE TABLE purchases (purchase_id serial, purchase_time timestamp, item text) partition by range on ((extract(year from purchase_time)),(extract(month from purchase_time)));
ERROR: referenced relation "purchases" is not a table or foreign table
Thom
Re: Declarative partitioning
От:
Thom Brown <thom@linux.com>
Дата:
On 19 August 2015 at 21:10, Josh Berkus <josh@agliodbs.com> wrote:
On 08/19/2015 04:59 AM, Simon Riggs wrote:
> I like the idea of a regular partitioning step because it is how you
> design such tables - "lets use monthly partitions".
>
> This gives sanely terse syntax, rather than specifying pages and pages
> of exact values in DDL....
>
> PARTITION BY RANGE ON (columns) INCREMENT BY (INTERVAL '1 month' )
> START WITH value;
Oh, I like that syntax!
How would it work if there were multiple columns? Maybe we don't want
to allow that for this form?
If we went with that, and had:
CREATE TABLE orders (order_id serial, order_date date, item text)
PARTITION BY RANGE ON (order_date) INCREMENT BY (INTERVAL '1 month')
START WITH '2015-01-01';
Where would the following go?
INSERT INTO orders (order_date, item) VALUES ('2014-11-12', 'Old item');
Would there automatically be an "others" partition? Or would it produce an error and act like a constraint?
Thom
Re: Declarative partitioning
От:
Michael Paquier <michael.paquier@gmail.com>
Дата:
On Thu, Aug 20, 2015 at 11:16 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
On 2015-08-19 PM 09:23, Simon Riggs wrote:
> We'll need regression tests that cover each restriction and docs that
> match. This is not something we should leave until last. People read the
> docs to understand the feature, helping them to reach consensus. So it is
> for you to provide the docs before, not wait until later. I will begin a
> code review once you tell me docs and tests are present. We all want the
> feature, so its all about the details now.
>
Sorry, should have added tests and docs already. I will add them in the
next version of the patch.
Yes, those would be really good to have before any review so as it is possible to grasp an understanding of what this patch does. I would like to look at it as well more in depths.
Thanks for willing to review.
--
Michael
Re: Declarative partitioning
От:
Pavan Deolasee <pavan.deolasee@gmail.com>
Дата:
On Tue, Aug 18, 2015 at 4:00 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
--
Hi,
I would like propose $SUBJECT for this development cycle. Attached is a
WIP patch that implements most if not all of what's described below. Some
yet unaddressed parts are mentioned below, too. I'll add this to the CF-SEP.
Syntax
======
1. Creating a partitioned table
CREATE TABLE table_name
PARTITION BY {RANGE|LIST}
ON (column_list);
Where column_list consists of simple column names or expressions:
PARTITION BY LIST ON (name)
PARTITION BY RANGE ON (year, month)
PARTITION BY LIST ON ((lower(left(name, 2)))
PARTITION BY RANGE ON ((extract(year from d)), (extract(month from d)))
How about HASH partitioning? Are there plans to support foreign tables as partitions?
Thanks,
Pavan
Pavan Deolasee http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
PostgreSQL Development, 24x7 Support, Training & Services
Re: Declarative partitioning
От:
Pavan Deolasee <pavan.deolasee@gmail.com>
Дата:
On Fri, Aug 21, 2015 at 11:22 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
On 2015-08-21 AM 06:27, David Fetter wrote:
>> By the last sentence, do you mean only UPDATEs to the partition key that
>> cause rows to jump partitions or simply any UPDATEs to the partition key?
>
> I don't know what Simon had in mind, but it seems to me that we have
> the following in descending order of convenience to users, and I
> presume, descending order of difficulty of implementation:
>
> 1. Updates propagate transparently, moving rows between partitions if needed.
>
> 2. Updates fail only if the change to a partition key would cause the
> row to have to move to another partition.
>
> 3. All updates to the partition key fail.
>
I was thinking I'd implement 2.
+1. IMHO thats a good starting point.
Thanks,
Pavan
Pavan Deolasee http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
PostgreSQL Development, 24x7 Support, Training & Services
Re: Declarative partitioning
От:
Corey Huinker <corey.huinker@gmail.com>
Дата:
Sorry for replying so late.
No worries! We have jobs to do aside from this.
Everything seemed to go dandy until I tried FOR VALUES (blah , blah],
where psql wouldn't send the command string without accepting the closing
parenthesis, :(. So maybe I should try to put the whole thing in '', that
is, accept the full range_spec in a string, but then we are back to
requiring full-blown range parse function which I was trying to avoid by
using the aforementioned grammar. So, I decided to move ahead with the
following grammar for time being:
START (lower-bound) [ EXCLUSIVE ]
| END (upper-bound) [ INCLUSIVE ]
| START (lower-bound) [ EXCLUSIVE ] END (upper-bound) [ INCLUSIVE ]
Where,
*-bound: a_expr
| *-bound ',' a_expr
Note that in the absence of explicit specification, lower-bound is
inclusive and upper-bound is exclusive.
Thanks for trying. I agree that it would be a full blown range parser, and I'm not yet advanced enough to help you with that.
So presently partitions that are unbounded on the lower end aren't possible, but that's a creation syntax issue, not an infrastructure issue. Correct?
Okay, perhaps I should not presume a certain usage. However, as you know,
the usage like yours requires some mechanism of data redistribution (also
not without some syntax), which I am not targeting with the initial patch.
I'm quite fine with limitations in this initial patch, especially if they don't limit what's possible in the future.
> Question: I haven't dove into the code, but I was curious about your tuple
> routing algorithm. Is there any way for the algorithm to begin it's scan of
> candidate partitions based on the destination of the last row inserted this
> statement? I ask because most use cases (that I am aware of) have data that
> would naturally cluster in the same partition.
No. Actually the tuple-routing function starts afresh for each row. For
range partitions, it's binary search over an array of upper bounds. There
is no row-to-row state caching in the partition module itself.
bsearch should be fine, that's what I've used in my own custom partitioning schemes.
Was there a new patch, and if so, is it the one you want me to kick the tires on?
Re: Declarative partitioning
От:
Corey Huinker <corey.huinker@gmail.com>
Дата:
The individual patches have commit messages that describe code changes.
This is registered in the upcoming CF. Feedback and review is greatly
welcomed!
Thanks,
Amit
We have a current system that is currently a mix of tables, each of which is range partitioned into approximately 15 partitions (using the pgxn range partitioning extension), and those partitions are themselves date-series partitioned via pg_partman. The largest table ingests about 100M rows per day in a single ETL. I will try this patch out and see how well it compares in handling the workload. Do you have any areas of interest or concern that I should monitor?
Re: Declarative partitioning
От:
Corey Huinker <corey.huinker@gmail.com>
Дата:
> So presently partitions that are unbounded on the lower end aren't
> possible, but that's a creation syntax issue, not an infrastructure issue.
> Correct?
In case it wasn't apparent, you can create those:
FOR VALUES END (upper-bound) [INCLUSIVE]
which is equivalent to:
FOR VALUES '(, upper-bound)' or FOR VALUES '(, upper-bound]'
Thanks for clarifying. My BNF-fu is weak.
Re: Declarative partitioning
От:
Corey Huinker <corey.huinker@gmail.com>
Дата:
So for now, you create an empty partitioned table specifying all the
partition keys without being able to define any partitions in the same
statement. Partitions (and partitions thereof, if any) will be created
using CREATE PARTITION statements, one for each.
...and I would assume that any attempt to insert into a partitioned table with no partitions (or lacking partitions at a defined level) would be an error? If so, I'd be ok with that.
Specifying range partitioning bound as PARTITION FOR RANGE <range-literal>
sounds like it offers some flexibility, which can be seen as a good thing.
But it tends to make internal logic slightly complicated.
Whereas, saying PARTITION FOR VALUES LESS THAN (max1, max2, ...) is
notationally simpler still and easier to work with internally. Also, there
will be no confusion about exclusivity of the bound if we document it so.
I understand wanting the internal rules to be simple. Oracle clearly went with VALUES LESS THAN waterfalls for that reason.
What I'm hoping to avoid is:
- having to identify my "year2014" partition by VALUES LESS THAN '2015-01-01', a bit of cognitive dissonance defining data by what it's not.
- and then hoping that there is a year2013 partition created by someone with similar sensibilities, the partition definition being incomplete outside of the context of other partition definitions.
- and then further hoping that nobody drops the year2013 partition, thus causing new 2013 rows to fall into the year2014 partition, a side effect of an operation that did not mention the year2014 partition.
Range types do that, and if we're concerned about range type overhead, we're only dealing with the ranges at DDL time, we can break down the ATR rules into a more easily digestible form once the partition is modified.
Range continuity can be tested with -|-, but we'd only need to test for overlaps: gaps in ranges are sometimes a feature, not a bug (ex: I don't want any rows from future dates and we weren't in business before 1997).
Also, VALUES LESS THAN forces us to use discrete values. There is no way with to express with VALUES LESS THAN partitions that have float values for temperature:
ice (,0.0), water [0.0,212.0], steam (212.0,3000.0], plasma (3000.0,).
ice (,0.0), water [0.0,212.0], steam (212.0,3000.0], plasma (3000.0,).
Yes, I can calculate the day after the last day in a year, I can use 212.0000000001, I can write code to rigorously check that all partitions are in place. I'd just rather not.
p.s. I'm really excited about what this will bring to Postgres in general and my organization in particular. This feature alone will help chip away at our needs for Vertica and Redshift clusters. Let me know if there's anything I can do to help.
Re: Declarative partitioning
От:
Corey Huinker <corey.huinker@gmail.com>
Дата:
I think we're converging on a good syntax, but I don't think the
choice of nothingness to represent an open range is a good idea, both
because it will probably create grammar conflicts now or later and
also because it actually is sort of confusing and unintuitive to read
given the rest of our syntax. I suggest using UNBOUNDED instead.
As much as it reminds me of the window syntax I loathe (ROWS BETWEEN UNBOUNDED ....gah), I'm inclined to agree with Robert here.
It also probably helps for code forensics in the sense that it's easier to text search for a something than a nothing.
Re: Declarative partitioning
От:
Corey Huinker <corey.huinker@gmail.com>
Дата:
My experiences with Oracle's hash function were generally not good -
there's a reason many hash algorithms exist. If/when we do hash
partitioning in Postgres I'd like to see the hash function be
user-definable.
+1
In my experience, hash partitioning had one use: When you had run out of ways to logically partition the data, AND you had two tables in a foreign key relationship, AND that relationship was the most common use-case for selecting the two tables. In which case, your one big join became 8 or 16 little ones, and all was well in the universe...
...until those little joins started to spill to disk, and now you need to repartition by 2x hashes, and that basically means a reload of your primary fact table, and a talk with a boss about why you painted yourself into a corner when you first did that.
Meanwhile, I think list and range are a good start. I'd prefer to see
sub-partitioning before hash partitioning.
+1
Re: Declarative partitioning
От:
Corey Huinker <corey.huinker@gmail.com>
Дата:
On Tue, Aug 18, 2015 at 6:30 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
Hi,
I would like propose $SUBJECT for this development cycle. Attached is a
WIP patch that implements most if not all of what's described below. Some
yet unaddressed parts are mentioned below, too. I'll add this to the CF-SEP.
Syntax
======
1. Creating a partitioned table
CREATE TABLE table_name
PARTITION BY {RANGE|LIST}
ON (column_list);
Where column_list consists of simple column names or expressions:
PARTITION BY LIST ON (name)
PARTITION BY RANGE ON (year, month)
PARTITION BY LIST ON ((lower(left(name, 2)))
PARTITION BY RANGE ON ((extract(year from d)), (extract(month from d)))
Note: LIST partition key supports only one column.
For each column, you could write operator class name:
PARTITION BY LIST/RANGE ON (colname [USING] opclass_name),
If not specified, the default btree operator class based on type of each
key column is used. If none of the available btree operator classes are
compatible with the partitioning strategy (list/range), error is thrown.
Built-in btree operator classes cover a good number of types for list and
range partitioning in practical scenarios.
A table created using this form is of proposed new relkind
RELKIND_PARTITIONED_REL. An entry in pg_partitioned_rel (see below) is
created to store partition key info.
Note: A table cannot be partitioned after-the-fact using ALTER TABLE.
Normal dependencies are created between the partitioned table and operator
classes, object in partition expressions like functions.
2. Creating a partition of a partitioned table
CREATE TABLE table_name
PARTITION OF partitioned_table_name
FOR VALUES values_spec;
Where values_spec is:
listvalues: [IN] (val1, ...)
rangevalues: START (col1min, ... ) END (col1max, ... )
| START (col1min, ... )
| END (col1max, ... )
A table created using this form has proposed pg_class.relispartition set
to true. An entry in pg_partition (see below) is created to store the
partition bound info.
The values_spec should match the partitioning strategy of the partitioned
table. In case of a range partition, the values in START and/or END should
match columns in the partition key.
Defining a list partition is fairly straightforward - just spell out the
list of comma-separated values. Error is thrown if the list of values
overlaps with one of the existing partitions' list.
CREATE TABLE persons_by_state (name text, state text)
PARTITION BY LIST ON (state);
CREATE TABLE persons_IL
PARTITION OF persons_by_state
FOR VALUES IN ('IL');
CREATE TABLE persons_fail
PARTITION OF persons_by_state
FOR VALUES IN ('IL');
ERROR: cannot create partition that overlaps with an existing one
For a range partition, there are more than one way:
Specify both START and END bounds: resulting range should not overlap with
the range(s) covered by existing partitions. Error is thrown otherwise.
Although rare in practice, gaps between ranges are OK.
CREATE TABLE measurement(logdate date NOT NULL)
PARTITION BY RANGE ON (logdate);
CREATE TABLE measurement_y2006m02
PARTITION OF measurement
FOR VALUES START ('2006-02-01') END ('2006-03-01'); --success
CREATE TABLE measurement_fail
PARTITION OF measurement
FOR VALUES START ('2006-02-15') END ('2006-03-01');
ERROR: cannot create partition that overlaps with an existing one
Specify only the START bound: add the partition on the left of some range
covered by existing partitions provided no overlap occurs (also
considering gaps between ranges, if any). If no such range exists, the new
partition will cover the range [START, +INFINITY) and become the rightmost
partition. Error is thrown if the specified START causes overlap.
CREATE TABLE measurement_y2006m01
PARTITION OF measurement
FOR VALUES START ('2006-01-01'); --success
CREATE TABLE measurement_fail
PARTITION OF measurement
FOR VALUES START ('2006-02-01'); --overlaps with measurement_y2006m02
ERROR: cannot create partition that overlaps with an existing one
Specify only the END bound: add the partition on the right of some range
covered by existing partitions provided no overlap occurs (also
considering gaps between ranges, if any). If no such range exists, the new
partition would cover the range (-INFINITY, END) and become the leftmost
partition. Error is thrown if the specified END causes overlap.
CREATE TABLE measurement_y2006m03
PARTITION OF measurement
FOR VALUES END ('2006-04-01'); --success
CREATE TABLE measurement_fail
PARTITION OF measurement
FOR VALUES END ('2006-03-01'); --overlaps with measurement_y2006m02
ERROR: cannot create partition that overlaps with an existing one
For each partition, START and END bound values are stored in the
catalog. Note that the lower bound is inclusive, whereas the upper bound
is exclusive.
Note: At most one range partition can have null min bound in which case it
covers the range (-INFINITY, END). Also, at most one range partition can
have null max bound in which case it covers the range [START, +INFINITY).
A normal dependency is created between the parent and the partition.
3. Multi-level partitioning
CREATE TABLE table_name
PARTITION OF partitioned_table_name
FOR VALUES values_spec
PARTITION BY {RANGE|LIST} ON (columns_list)
This variant implements a form of so called composite or sub-partitioning
with arbitrarily deep partitioning structure. A table created using this
form has both the relkind RELKIND_PARTITIONED_REL and
pg_class.relispartition set to true.
4. (yet unimplemented) Attach partition (from existing table)
ALTER TABLE partitioned_table
ATTACH PARTITION partition_name
FOR VALUES values_spec
USING [TABLE] table_name;
ALTER TABLE table_name
SET VALID PARTITION OF <parent>;
The first of the above pair of commands would attach table_name as a (yet)
'invalid' partition of partitioned_table (after confirming that it matches
the schema and does not overlap with other partitions per FOR VALUES
spec). It would also record the FOR VALUES part in the partition catalog
and set pg_class.relispartition to true for table_name.
After the first command is done, the second command would take exclusive
lock on table_name, scan the table to check if it contains any values
outside the boundaries defined by FOR VALUES clause defined previously,
throw error if so, mark as valid partition of parent if not.
Does that make sense?
5. Detach partition
ALTER TABLE partitioned_table
DETACH PARTITION partition_name [USING table_name]
This removes partition_name as partition of partitioned_table. The table
continues to exist with the same name or 'table_name', if specified.
pg_class.relispartition is set to false for the table, so it behaves like
a normal table.
System catalogs
===============
1. pg_partitioned_rel
CATALOG(pg_partitioned_rel,oid) BKI_WITHOUT_OIDS
{
Oid partrelid; /* partitioned table pg_class.oid */
char partstrategy; /* partitioning strategy 'l'/'r' */
int16 partnatts; /* number of partition columns */
int2vector partkey; /* column numbers of partition columns;
* 0 where specified column is an
* expresion */
oidvector partclass; /* operator class to compare keys */
pg_node_tree partexprs; /* expression trees for partition key
* members that are not simple column
* references; one for each zero entry
* in partkey[] */
};
2. pg_partition (omits partisvalid alluded to above)
CATALOG(pg_partition,oid) BKI_WITHOUT_OIDS
{
Oid partitionid; /* partition oid */
Oid partparent; /* parent oid */
anyarray partlistvalues; /* list of allowed values of the only
* partition column */
anyarray partrangebounds; /* list of bounds of ranges of
* allowed values per partition key
* column */
};
Further notes
=============
There are a number of restrictions on performing after-the-fact changes
using ALTER TABLE to partitions (ie, relispartition=true):
* Cannot add/drop column
* Cannot set/drop OIDs
* Cannot set/drop NOT NULL
* Cannot set/drop default
* Cannot alter column type
* Cannot add/drop alter constraint (table level)
* Cannot change persistence
* Cannot change inheritance
* Cannot link to a composite type
Such changes should be made to the topmost parent in the partitioning
hierarchy (hereafter referred to as just parent). These are recursively
applied to all the tables in the hierarchy. Although the last two items
cannot be performed on parent either.
Dropping a partition using DROP TABLE is not allowed. It needs to detached
using ALTER TABLE on parent before it can be dropped as a normal table.
Triggers on partitions are not allowed. They should be defined on the
parent. That said, I could not figure out a way to implement row-level
AFTER triggers on partitioned tables (more about that in a moment); so
they are currently not allowed:
CREATE TRIGGER audit_trig
AFTER INSERT ON persons
FOR EACH ROW EXECUTE PROCEDURE audit_func();
ERROR: Row-level AFTER triggers are not supported on partitioned tables
Column/table constraints on partitions are not allowed. They should be
defined on the parent. Foreign key constraints are not allowed due to
above limitation (no row-level after triggers).
A partitioning parent (RELKIND_PARTITIONED_REL) does not have storage.
Creating index on parent is not allowed. They should be defined on (leaf)
partitions. Because of this limitation, primary keys are not allowed on a
partitioned table. Perhaps, we should be able to just create a dummy
entry somewhere to represent an index on parent (which every partition
then copies.) Then by restricting primary key to contain all partition key
columns, we can implement unique constraint over the whole partitioned
table. That will in turn allow us to use partitioned tables as PK rels in
a foreign key constraint provided row-level AFTER trigger issue is resolved.
VACUUM/ANALYZE on individual partitions should work like normal tables.
I've not implemented something like inheritance tree sampling for
partitioning tree in this patch. Autovacuum has been taught to ignore
parent tables and vacuum/analyze partitions normally.
Dropping a partitioned table should (?) unconditionally drop all its
partitions probably but, currently the patch uses dependencies, so
requires to specify CASCADE to do the same.
What should TRUNCATE on partitioned table do?
Ownership, privileges/permissions, RLS should be managed through the
parent table although not comprehensively addressed in the patch.
There is no need to define tuple routing triggers. CopyFrom() and
ExecInsert() determine target partition just before performing
heap_insert() and ExecInsertIndexTuples(). IOW, any BR triggers and
constraints (on parent) are executed for tuple before being routed to a
partition. If no partition can be found, it's an error.
Because row-level AFTER triggers need to save ItemPointers in trigger
event data and defining triggers on partitions (which is where tuples
really go) is not allowed, I could not find a straightforward way to
implement them. So, perhaps we should allow (only) row-level AFTER
triggers on partitions or think of modifying trigger.c to know about this
twist explicitly.
Internal representations
========================
For a RELKIND_PARTITIONED_REL relations, RelationData gets a few new
fields to store the partitioning metadata. That includes partition key
tuple (pg_partitioned_rel) including some derived info (opfamily,
opcintype, compare proc FmgrInfos, partition expression trees).
Additionally, it also includes a PartitionInfo object which includes
partition OIDs array, partition bound arrays (if range partitioned,
rangemax is sorted in ascending order and OIDs are likewise ordered). It
is built from information in pg_partition catalog.
While RelationBuildDesc() initializes the basic key info, fields like
expression trees, PartitionInfo are built on demand and cached. For
example, InitResultRelInfo() builds the latter to populate the newly added
ri_PartitionKeyInfo and ri_Partitions fields, respectively.
PartitionInfo object is rebuilt on every cache invalidation of the rel
which includes when adding/attaching/detaching a new partition.
Planner and executor considerations
=====================================
The patch does not yet implement any planner changes for partitioned
tables, although I'm working on the same and post updates as soon as
possible. That means, it is not possible to run SELECT/UPDATE/DELETE
queries on partitioned tables without getting:
postgres=# SELECT * FROM persons;
ERROR: could not open file "base/13244/106975": No such file or directory
Given that there would be more direct ways of performing partition pruning
decisions with the proposed, it would be nice to utilize them.
Specifically, I would like to avoid having to rely on constraint exclusion
for partition pruning whereby subquery_planner() builds append_rel_list
and the later steps exclude useless partitions.
By extending RelOptInfo to include partitioning info for partitioned rels,
it might be possible to perform partition pruning directly without
previously having to expand them. Although, as things stand now, it's not
clear how that might work - when would partition RTEs be added to the
rtable? The rtable is assumed not to change after
setup_simple_rel_arrays() has done its job which is much earlier than when
it would be desirable for the partitioned table expansion (along with
partition pruning) to happen. Moreover, if that means we might not be able
to build RelOptInfo's for partitions, how to choose best paths for them
(index paths or not, etc.)?
I'm also hoping we don't require something like inheritance_planner() for
when partitioned tables are target rels. I assume considerations for why
the special processing is necessary for inheritance trees in that scenario
don't apply to partitioning trees. So, if grouping_planner() returns a
Append plan (among other options) for the partitioning tree, tacking a
ModifyTable node on top should do the trick?
Suggestions greatly welcome in this area.
Other items
===========
Will include the following once we start reaching consensus on main parts
of the proposed design/implementation:
* New regression tests
* Documentation updates
* pg_dump, psql, etc.
For reference, some immediately previous discussions:
* On partitioning *
http://www.postgresql.org/message-id/20140829155607.GF7705@eldon.alvh.no-ip.org
* Partitioning WIP patch *
http://www.postgresql.org/message-id/54EC32B6.9070605@lab.ntt.co.jp
Comments welcome!
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Quick thoughts borne of years of slugging it out with partitions on Oracle:
- Finally!!!!!!!!!!!
- Your range partitioning will need to express exclusive/inclusive bounds, or go to the Oracle model where every partition is a cascading "values less than" test context dependent on the partitions defined before it. I would suggest that leveraging existing range types (or allowing the user to specify a range type, like for a specific collation of a text range) would allow for the most flexible and postgres-ish range definition. You seem to do this with the "[USING] opclass_name" bit, but don't follow through on the START(...) and END(...). Something like FOR VALUES <@ '[''2014-01-01'',''2015-01-01)'::daterange would cover most needs succinctly, though I admit the syntax for complex ranges could be cumbersome, though something like FOR VALUES <@ '[(''a'',1),(''b'',1))'::letter_then_number_range is still readable.
- No partitioning scheme survives first contact with reality. So you will need a facility for splitting and joining existing partitions. For splitting partitions, it's sufficient to require that the new partition share either a upper/lower bound (with the same inclusivity/exclusivity) of an existing partition, thus uniquely identifying the partition to be split, and require that the other bound be within the range of the partition to be split. Similarly, it's fair to require that the partitions to be joined be adjacent in range. In both cases, range operators make these tests simple.
- Your features 4 and 5 are implemented in Oracle with SWAP PARTITION, which is really neat for doing ETLs and index rebuilds offline in a copy table, and then swapping the data segment of that table with the partition specified. Which could be considered cheating because none of the partition metadata changed, just the pointers to the segments. We already do this with adding removing INHERIT. I'm not saying they can't be separate functionality, but keeping an atomic SWAP operation would be grand.
Re: Declarative partitioning
От:
Corey Huinker <corey.huinker@gmail.com>
Дата:
Hm, I see. How about multi-column keys? Do we care enough about that use
case? I don't see a nice-enough-looking range literal for such keys.
Consider for instance,
With the partitioned table defined as:
CREATE TABLE foo(c1 char(2), c2 char(2)) PARTITION BY RANGE (c1, c2);
Good question! I would assume that we'd use a syntax that presumes c1 and c2 are a hypothetical composite type. But what does that look like?
To answer it, I tried this:
# create type duple as (a text, b text);
CREATE TYPE
# create type duplerange as range (subtype = duple);
CREATE TYPE
# select '(beebop,alula)'::duple;
duple
----------------
(beebop,alula)
(1 row)
# select '("hey ho","letsgo")'::duple;
duple
-------------------
("hey ho",letsgo)
(1 row)
analytics=# select duplerange('(beebop,alula)','("hey ho","letsgo")','(]');
duplerange
------------------------------------------
("(beebop,alula)","(""hey ho"",letsgo)"]
(1 row)
So I would assume that we'd use a syntax that presumed the columns were in a composite range type.
Which means your creates would look like (following Robert Haas's implied suggestion that we leave off the string literal quotes):
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES ( , (b,2) );
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (b,2), (b,3) );
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (b,3), (b,4) );
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (b,4), (c,2) );
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (c,2), (c,3) );
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (c,3), (c,4) );
That's not terrible looking.
We would want to also think about what subset of many permutations of this
syntax to accept range specs for new partitions. Mostly to preserve the
non-overlapping invariant and I think it would also be nice to prevent gaps.
Gaps *might* be intentional. I can certainly see where we'd want to set up warnings for discontinuity, or perhaps some utility functions:
pg_partitions_ranges_are_continuous('master_table_name')
pg_partitions_are_adjacent('master_table_name','p1','p2')
But for the most part, range partitions evolve from splits when one partition grows too big, so that won't be such a problem.
Consider that once we create:
PARTITION FOR VALUES [current_date,);
Now to create a new partition starting at later date, we have to have a
"split partition" feature which would scan the above partition to
distribute the existing data rows appropriately to the resulting two
partitions. Right?
Correct. And depending on policy, that might be desirable and might be not.
If the table were for death records, we'd very much want to reject rows in the future, if only to avoid upsetting the person.
If the table were of movie release dates, we'd *expect* that only dates (,current_date] would be entered, but if someone chose to leak a release date, we'd want to capture that and deal with it later.
So yeah, we're going to (eventually) need a SPLIT PARTITION that migrates rows to a new partition.
IOW, one shouldn't create an unbounded partition if more partitions in the
unbounded direction are expected to be created. It would be OK for
unbounded partitions to be on the lower end most of the times.
On this I'll have to disagree. My own use case where I use my range_partitioning extension starts off with a single partition () and all new partitions are splits of that. The ranges evolve over time as partitions grow and slow down. It's nice because we're not trying to predict where growth will be, we split where growth is.
> p.s. Sorry I haven't been able to kick the tires just yet. We have a very
> good use case for this, it's just a matter of getting a machine and the
> time to devote to it.
I would appreciate it. You could wait a little more for my next
submission which will contain some revisions to the tuple routing code.
Ok, I'll wait a bit. In the mean time I can tell you a bit about the existing production system I'm hoping to replicate in true partitioning looks like this:
Big Master Table:
Range partition by C collated text
Date Range
Date Range
Date Range
...
Range partition by C collated text
Date Range
Date Range
...
...
Currently this is accomplished through my range_partitioning module, and then using pg_partman on those partitions. It works, but it's a lot of moving parts.
Currently this is accomplished through my range_partitioning module, and then using pg_partman on those partitions. It works, but it's a lot of moving parts.
The machine will be a 32 core AWS box. As per usual with AWS, it will be have ample memory and CPU, and be somewhat starved for I/O.
Question: I haven't dove into the code, but I was curious about your tuple routing algorithm. Is there any way for the algorithm to begin it's scan of candidate partitions based on the destination of the last row inserted this statement? I ask because most use cases (that I am aware of) have data that would naturally cluster in the same partition.
Re: Declarative partitioning
От:
Corey Huinker <corey.huinker@gmail.com>
Дата:
If we have a CREATE statement for each partition, how do we generalize
that to partitions at different levels? For example, if we use something
like the following to create a partition of parent_name:
CREATE PARTITION partition_name OF parent_name FOR VALUES ...
WITH ... TABLESPACE ...
Do we then say:
CREATE PARTITION subpartition_name OF partition_name ...
That's how I'd want it for partitions created after the initial partitioned table is created.
I'd like to be able to identify the parent partition by it's own partitioning parameters rather than name, like the way we can derive the name of an index in ON CONFLICT. But I see no clean way to do that, and if one did come up, we'd simply allow the user to replace
<partition_name>
with
table_name PARTITION partition_spec [...PARTITION partition_spec [ ...PARTITION turtles_all_the_way_down]]).
Again, totally fine with forcing the maintenance script to know or discover the name of the partition to be subpartitioned...for now.
to create a level 2 partition (sub-partition) of parent_name? Obviously,
as is readily apparent from the command, it is still a direct partition of
partition_name for all internal purposes (consider partition list caching
in relcache, recursive tuple routing, etc.) save some others.
I ask that also because it's related to the choice of syntax to use to
declare the partition key for the multi-level case. I'm considering the
SUBPARTITION BY notation and perhaps we could generalize it to more than
just 2 levels. So, for the above case, parent_name would have been created as:
CREATE TABLE parent_name PARTITION BY ... SUBPARTITION BY ...
Needless to say, when subpartition_name is created with the command we saw
a moment ago, the root partitioned table would be locked. In fact, adding
a partition anywhere in the hierarchy needs an exclusive lock on the root
table. Also, partition rule (the FOR VALUES clause) would be validated
against PARTITION BY or SUBPARTITION BY clause at the respective level.
Although, I must admit I feel a little uneasy about the inherent asymmetry
in using SUBPARTITION BY for key declaration whereas piggybacking CREATE
PARTITION for creating sub-partitions. Is there a better way?
Provided that the syntax allows for N levels of partitioning, I don't care if it's
PARTITION BY.., PARTITION BY..., PARTITION BY ...
or
PARTITION BY.., SUBPARTITION BY..., SUBPARTITION BY ...
The first is probably better for meta-coding purposes, but the second makes it clear which partition layer is first.
> As for the convenience syntax (if at all), how about:
>
> CREATE TABLE foo (
> ...
> )
> PARTITION BY ... ON (...)
> SUBPARTITION BY ... ON (...)
> opt_partition_list;
>
> where opt_partition_list is:
>
> PARTITIONS (
> partname FOR VALUES ... [WITH] [TABLESPACE] opt_subpart_list
> [, ...]
> )
>
> where opt_subpart_list is:
>
> SUBPARTITIONS (
> subpartname FOR VALUES ... [WITH] [ TABLESPACE]
> [, ...]
> )
Do we want this at all? It seems difficult to generalize this to
multi-level hierarchy of more than 2 levels.
I want this.
Granted the syntax of a 3+ level partitioning would be cumbersome, but it is what the user wanted, and the nested PARTITION/SUBPARTITION. In those cases, the user might opt to not create more than the default first subpartition to keep the syntax sane, or we might auto-generate default partitions (with a VALUES clause of whatever "all values" is for that datatype...again, this is an area where leveraging range types would be most valuable).
On one hand, I think to keep treating "partition hierarchies" as
"inheritance hierachies" might have some issues. I am afraid that
documented inheritance semantics may not be what we want to keep using for
the new partitioned tables. By that, I mean all the user-facing behaviors
where inheritance has some bearing. Should it also affect new partitioned
tables? Consider whether inheritance semantics would render infeasible
some of the things that we'd like to introduce for the new partitioned
tables such as automatic tuple routing, or keep us from improving planner
smarts and executor capabilities for partitioned tables over what we
already have.
I feel that Automatic tuple routing should be considered they key benefit of "real" partitions over inherited tables. Trigger maintenance is most of the work of custom partitioning schemes, at least the ones I've written.
There's a great chance that not everyone cares right now about this part
of the new partitioning but just want to put it out there. There are more
contentious issues like the syntax, partitioning maintenance commands that
we plan to support (now or later) and such.
What I've read so far addresses most of my concerns.
Still somewhat on my mind:
1. ability to describe partition bounds via range types, regardless of whether the Automatic Tuple Routing uses those types internally.
2. syntax for splitting a partition in two, merging two adjacent partitions (you probably touched on these earlier and I missed it or forgot).
3. ability to swap a partition with a table not currently associated with the partitioned table.
4. The applicability of this syntax to materialized views, allowing us to do REFRESH CONCURRENTLY a few parts at a time, or only refreshing the data we know needs it.
Items 2 and 3 don't have to be implemented right away, as they're separate ALTER commands. 4 is a pipe dream. With Item 1 I ask only that we don't pick a syntax that prevents description via range types.
Re: Declarative partitioning
От:
Corey Huinker <corey.huinker@gmail.com>
Дата:
It seems the way of specifying per-partition definition/constraint,
especially for range partitioning, would have a number of interesting
alternatives.
By the way, the [USING opclass_name] bit is just a way of telling that a
particular key column has user-defined notion of "ordering" in case of
range partitioning and "equality" for list partitioning. The opclass would
eventually determine which WHERE clauses (looking at operators, operand
types) are candidates to help prune partitions. If we use the range_op
range_literal::range_type notation to describe partition constraint for
each partition, it might not offer much beyond the readability. We are not
really going to detect range operators being applied in WHERE conditions
to trigger partition pruning, for example. Although I may be missing
something...
I don't think it's important that ranges be used internally for partition pruning, though I don't see a reason why they couldn't. I was making the case for range types being used at partition creation/declaration time, because you were proposing new syntax to describe a range of values in text when we already have that in range types. We should eat our own dog food.
But mostly, I wanted to make sure that range partitions could have inclusive and exclusive bounds.
> - No partitioning scheme survives first contact with reality. So you will
> need a facility for splitting and joining existing partitions. For
> splitting partitions, it's sufficient to require that the new partition
> share either a upper/lower bound (with the same inclusivity/exclusivity) of
> an existing partition, thus uniquely identifying the partition to be split,
> and require that the other bound be within the range of the partition to be
> split. Similarly, it's fair to require that the partitions to be joined be
> adjacent in range. In both cases, range operators make these tests simple.
>
SPLIT/MERGE can be done in later patches/release, I think.
Later patches for sure. When a partition "fills up", it's a critical performance issue, so the fewer steps needed to amend it the better.
Re: Declarative partitioning
От:
Corey Huinker <corey.huinker@gmail.com>
Дата:
Also, you won't see any optimizer and executor changes. Queries will still
use the same plans as existing inheritance-based partitioned tables,
although as I mentioned, constraint exclusion won't yet kick in. That will
be fixed very shortly.
And of course, comments on syntax are welcome as well.
Thanks,
Amit
Good to know the current limitations/expectations.
Our ETL has a great number of workers that do something like this:
1. grab a file
2. based on some metadata of that file, determine the partition that that would receive ALL of the rows in that file. It's actually multiple tables, all of which are partitioned, all of which fully expect the file data to fit in exactly one partition.
3. \copy into a temp table
4. Transform the data and insert the relevant bits into each of the target partitions derived in #2.
So while ATR is a major feature of true partitioning, it's not something we'd actually need in our current production environment, but I can certainly code it that way to benchmark ATR vs "know the destination partition ahead of time" vs "insane layered range_partitioning trigger + pg_partman trigger".
Currently we don't do anything like table swapping, but I've done that enough in the past that I could probably concoct a test of that too, once it's implemented.
As for the syntax, I'm not quite sure your patch addresses the concerned I voiced earlier: specifically if the VALUES IN works for RANGE as well as LIST, but I figured that would become clearer once I tried to actually use it. Currently we have partitioning on C-collated text ranges (no, they don't ship with postgres, I had to make a custom type) something like this:
part0: (,BIG_CLIENT)part1: [BIG_CLIENT,BIG_CLIENT]part2: (BIG_CLIENT,L)part3: [L,MONSTROUSLY_BIG_CLIENT)part4: [MONSTROUSLY_BIG_CLIENT,MONSTROUSLY_BIG_CLIENT]part5: (MONSTROUSLY_BIG_CLIENT,RANDOM_CLIENT_LATE_IN_ALPHABET]part6: (RANDOM_CLIENT_LATE_IN_ALPHABET,)
I can't implement that with a simple VALUES LESS THAN clause, unless I happen to know 'x' in 'BIG_CLIENTx', where 'x' is the exact first character in the collation sequence, which has to be something unprintable, and that would make those who later read my code to say something unprintable. So yeah, I'm hoping there's some way to cleanly represent such ranges.
Re: Declarative partitioning
От:
Corey Huinker <corey.huinker@gmail.com>
Дата:
On Thu, Feb 18, 2016 at 12:41 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
START [ EXCL ] (startval) END [ INCL ] (endval)
That is, in range type notation, '[startval, endval)' is the default
behavior. So for each partition, there is at least the following pieces of
metadata:
This is really close, and if it is what we ended up with we would be able to use it.
I suggest that the range notation can be used even when no suitable range type exists.
I assume the code for parsing a range spec regardless of data type already exists, but in case it doesn't, take a range spec of unknown type:
[x,y)
x and y are either going to be raw strings or doublequoted strings with possible doublequote escapes, each of which would be coercible into the the type of the partition column.
In other words, if your string values were 'blah , blah ' and 'fabizzle', the [) range spec would be ["blah , blah ",fabizzle).
Using regular range specs syntax also allows for the range to be unbounded in either or both directions, which is a possibility, especially in newer tables where the expected distribution of data is unknown.
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
Hi Amit,
I tried creating partitioned table by range on an expression likeCREATE TABLE pt1_e (a int, b int, c varchar) PARTITION BY RANGE(a + b);
ERROR: syntax error at or near "+"
LINE 1: ...E pt1_e (a int, b int, c varchar) PARTITION BY RANGE(a + b);
On Thu, Jun 9, 2016 at 7:20 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
On 2016/06/08 22:22, Ashutosh Bapat wrote:
> On Mon, May 23, 2016 at 3:35 PM, Amit Langote wrote
>>
>> [...]
>>
>> I made a mistake in the last version of the patch which caused a relcache
>> field to be pfree'd unexpectedly. Attached updated patches.
>
> 0003-... patch does not apply cleanly. It has some conflicts in pg_dump.c.
> I have tried fixing the conflict in attached patch.
Thanks. See attached rebased patches.
Regards,
Amit
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
I am seeing following warning with this set of patches.
gram.y:4734:24: warning: assignment from incompatible pointer type [enabled by default]
gram.y:4734:24: warning: assignment from incompatible pointer type [enabled by default]
On Tue, Jul 5, 2016 at 10:18 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
On 2016/07/04 21:31, Ashutosh Bapat wrote:
> Hi Amit,
> I observed that the ChangeVarNodes call at line 1229 in
> get_relation_constraints() (code below) changes the varno in the cached
> copy of partition key expression, which is undesirable. The reason for this
> seems to be that the RelationGetPartitionCheckQual() returns the copy of
> partition key expression directly from the cache. This is mostly because
> get_check_qual_for_range() directly works on cached copy of partition key
> expressions, which it should never.
Yes, a copyObject() on key->partexprs items seems necessary. Will fix that.
> 1223 /* Append partition check quals, if any */
> 1224 pcqual = RelationGetPartitionCheckQual(relation);
> 1225 if (pcqual)
> 1226 {
> 1227 /* Fix Vars to have the desired varno */
> 1228 if (varno != 1)
> 1229 ChangeVarNodes((Node *) pcqual, 1, varno, 0);
> 1230
> 1231 result = list_concat(result, pcqual);
> 1232 }
>
> Because of this, the first time through the partition key expressions are
> valid, but then onwards they are restamped with the varno of the first
> partition.
>
> Please add testcases to your patch to catch such types of issues.
I will integrate tests into the patch(es) and add some more.
Thanks,
Amit
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
The lists for list partitioned tables are stored as they are specified by the user. While searching for a partition to route tuple to, we compare it with every list value of every partition. We might do something better similar to what's been done to range partitions. The list of values for a given partition can be stored in ascending/descending sorted order. Thus a binary search can be used to check whether given row's partition key column has same value as one in the list. The partitions can then be stored in the ascending/descending order of the least/greatest values of corresponding partitions. We might be able to eliminate search in a given partition if its lowest value is higher than the given value or its higher value is lower than the given value.
On Thu, Jul 21, 2016 at 10:10 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
On 2016/07/19 22:53, Ashutosh Bapat wrote:
> I am seeing following warning with this set of patches.
> gram.y:4734:24: warning: assignment from incompatible pointer type [enabled
> by default]
Thanks, will fix. Was a copy-paste error.
Thanks,
Amit
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
FOR VALUE clause of a partition does not allow a constant expression like (10000/5 -1). It gives syntax error
regression=# create table pt1_p1 partition of pt1 for values start (0) end ((10000/5) - 1);
ERROR: syntax error at or near "("
LINE 1: ...pt1_p1 partition of pt1 for values start (0) end ((10000/5) ...
regression=# create table pt1_p1 partition of pt1 for values start (0) end ((10000/5) - 1);
ERROR: syntax error at or near "("
LINE 1: ...pt1_p1 partition of pt1 for values start (0) end ((10000/5) ...
On Tue, Aug 9, 2016 at 12:48 PM, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote:
What strikes me odd about these patches is RelOptInfo has remained unmodified. For a base partitioned table, I would expect it to be marked as partitioned may be indicating the partitioning scheme. Instead of that, I see that the code directly deals with PartitionDesc, PartitionKey which are part of Relation. It uses other structures like PartitionKeyExecInfo. I don't have any specific observation as to why we need such information in RelOptInfo, but lack of it makes me uncomfortable. It could be because inheritance code does not require any mark in RelOptInfo and your patch re-uses inheritance code. I am not sure.--On Tue, Aug 9, 2016 at 9:17 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote: On 2016/08/09 6:02, Robert Haas wrote:
> On Mon, Aug 8, 2016 at 1:40 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> +1, if we could do it. It will need a change in the way Amit's patch stores
>>> partitioning scheme in PartitionDesc.
>>
>> Okay, I will try to implement this in the next version of the patch.
>>
>> One thing that comes to mind is what if a user wants to apply hash
>> operator class equality to list partitioned key by specifying a hash
>> operator class for the corresponding column. In that case, we would not
>> have the ordering procedure with an hash operator class, hence any
>> ordering based optimization becomes impossible to implement. The current
>> patch rejects a column for partition key if its type does not have a btree
>> operator class for both range and list methods, so this issue doesn't
>> exist, however it could be seen as a limitation.
>
> Yes, I think you should expect careful scrutiny of that issue. It
> seems clear to me that range partitioning requires a btree opclass,
> that hash partitioning requires a hash opclass, and that list
> partitioning requires at least one of those things. It would probably
> be reasonable to pick one or the other and insist that list
> partitioning always requires exactly that, but I can't see how it's
> reasonable to insist that you must have both types of opclass for any
> type of partitioning.
So because we intend to implement optimizations for list partition
metadata that presuppose existence of corresponding btree operator class,
we should just always require user to specify one (or error out if user
doesn't specify and a default one doesn't exist). That way, we explicitly
do not support specify hash equality operator for list partitioning.
>> So, we have 3 choices for the internal representation of list partitions:
>>
>> Choice 1 (the current approach): Load them in the same order as they are
>> found in the partition catalog:
>>
>> Table 1: p1 {'b', 'f'}, p2 {'c', 'd'}, p3 {'a', 'e'}
>> Table 2: p1 {'c', 'd'}, p2 {'a', 'e'}, p3 {'b', 'f'}
>>
>> In this case, mismatch on the first list would make the two tables
>> incompatibly partitioned, whereas they really aren't incompatible.
>
> Such a limitation seems clearly unacceptable. We absolutely must be
> able to match up compatible partitioning schemes without getting
> confused by little details like the order of the partitions.
Agreed. Will change my patch to adopt the below method.
>> Choice 2: Representation with 2 arrays:
>>
>> Table 1: ['a', 'b', 'c', 'd', 'e', 'f'], [3, 1, 2, 2, 3, 1]
>> Table 2: ['a', 'b', 'c', 'd', 'e', 'f'], [2, 3, 1, 1, 2, 3]
>>
>> It still doesn't help the case of pairwise joins because it's hard to tell
>> which value belongs to which partition (the 2nd array carries the original
>> partition numbers). Although it might still work for tuple-routing.
>
> It's very good for tuple routing. It can also be used to match up
> partitions for pairwise joins. Compare the first arrays. If they are
> unequal, stop. Else, compare the second arrays, incrementally
> building a mapping between them and returning false if the mapping
> turns out to be non-bijective. For example, in this case, we look at
> index 0 and decide that 3 -> 2. We look at index 1 and decide 1 -> 3.
> We look at index 2 and decide 2 -> 1. We look at index 4 and find
> that we already have a mapping for 2, but it's compatible because we
> need 2 -> 1 and that's what is already there. Similarly for the
> remaining entries. This is really a pretty easy loop to write and it
> should run very quickly.
I see, it does make sense to try to implement this way.
>> Choice 3: Order all lists' elements for each list individually and then
>> order the lists themselves on their first values:
>>
>> Table 1: p3 {'a', 'e'}, p2 {'b', 'f'}, p1 {'c', 'd'}
>> Table 2: p2 {'a', 'e'}, p1 {'b', 'f'}, p3 {'c', 'd'}
>>
>> This representation makes pairing partitions for pairwise joining
>> convenient but for tuple-routing we still need to visit each partition in
>> the worst case.
>
> I think this is clearly not good enough for tuple routing. If the
> algorithm I proposed above turns out to be too slow for matching
> partitions, then we could keep both this representation and the
> previous one. We are not limited to just one. But I don't think
> that's likely to be the case.
I agree. Let's see how the option 2 turns out.
> Also, note that all of this presupposes we're doing range
> partitioning, or perhaps list partitioning with a btree opclass. For
> partitioning based on a hash opclass, you'd organize the data based on
> the hash values rather than range comparisons.
Yes, the current patch does not implement hash partitioning, although I
have to think about how to support the hash case when designing the
internal data structures.
By the way, I am planning to start a new thread with the latest set of
patches which I will post in a day or two. I have tried to implement all
the bug fixes and improvements that have been suggested on this thread so
far. Thanks to all those who reviewed and gave their comments. Please
check this page to get a link to the new thread:
https://commitfest.postgresql.org/10/611/
Thanks,
AmitBest Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
On Fri, Apr 15, 2016 at 10:30 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Apr 15, 2016 at 5:46 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
> Retaining the partition hierarchy would help to push-down join across
> partition hierarchy effectively.
-1. You don't get to insert cruft into the final plan for the
convenience of the optimizer. I think the AppendPath needs to be
annotated with sufficient information to do whatever query planning
optimizations we want, and some or all of that may need to carry over
to the Append plan to allow run-time partition pruning. But I think
that flattening nests of Appends is a good optimization and we should
preserve it. If that makes the additional information that any given
Append needs to carry a bit more complex, so be it.
I also think it's very good that Amit has kept the query planner
unchanged in this initial patch. Let's leave that work to phase two.
What I suggest we do when the time comes is invent new nodes
RangePartitionMap, ListPartitionMap, HashPartitionMap. Each contains
minimal metadata needed for tuple routing or planner transformation.
For example, RangePartitionMap can contain an array of partition
boundaries - represented as Datums - and an array of mappings, each a
Node *. The associated value can be another PartitionMap object if
there is subpartitioning in use, or an OID.
range table index instead of OID to make it easy to lookup the
This can be used both for
matching up partitions for join pushdown, and also for fast tuple
routing and runtime partition pruning.
I was thinking about join pushdown (in partitioning hierarchy) of multiple tables which have similar partitioning structure for few upper levels but the entire partitioning hierarchy does not match. Pushing down the join as much possible into the partition hierarchy will help. We might end up with joins between a plain table and Append relation at the leaf level. For such join push-down it looks like we will have to construct corresponding Append hierarchy, push the joins down and then (may be) collapse it to just collect the results of joins at the leaf level. But preparing for that kind of optimization need not be part of this work.
> 2. The new syntax allows CREATE TABLE to be specified as partition of an
> already partitioned table. Is it possible to do the same for CREATE FOREIGN
> TABLE? Or that's material for v2? Similarly for ATTACH PARTITION.
+1 for making CREATE FOREIGN TABLE support that also, and in version
1. And same for ATTACH PARTITION.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
Hi Amit,
I am trying multi-column/expression partitions.create table t1_multi_col (a int, b int) partition by range (a, b);
create table t1_mc_p1 partition of t1_multi_col for values start (1, 200) end (100, 300);
create table t1_mc_p2 partition of t1_multi_col for values start (200, 1) end (300, 100);
insert into t1_multi_col values (1, 250);
insert into t1_multi_col values (250, 1);
insert into t1_multi_col values (100, 100);
select tableoid::regclass, * from t1_multi_col;
tableoid | a | b
----------+-----+-----
t1_mc_p1 | 1 | 250
t1_mc_p1 | 100 | 100
t1_mc_p2 | 250 | 1
Symantec of multiple columns for ranges (may be list as well) looks confusing. The current scheme doesn't allow overlapping range for one of the partitioning keys even if the combined range is non-overlapping.
create table t1_multi_col (a int, b int) partition by range (a, b);
create table t1_mc_p1 partition of t1_multi_col for values start (1, 100) end (100, 200);
create table t1_mc_p2 partition of t1_multi_col for values start (1, 200) end (100, 300);
ERROR: new partition's range overlaps with that of partition "t1_mc_p1" of "t1_multi_col"
HINT: Please specify a range that does not overlap with any existing partition's range.
create table t1_mc_p2 partition of t1_multi_col for values start (1, 300) end (100, 400);
ERROR: new partition's range overlaps with that of partition "t1_mc_p1" of "t1_multi_col"
HINT: Please specify a range that does not overlap with any existing partition's range.
create table t1_multi_col (a int, b int) partition by range (a, b);
create table t1_mc_p1 partition of t1_multi_col for values start (1, 100) end (100, 200);
create table t1_mc_p2 partition of t1_multi_col for values start (1, 200) end (100, 300);
ERROR: new partition's range overlaps with that of partition "t1_mc_p1" of "t1_multi_col"
HINT: Please specify a range that does not overlap with any existing partition's range.
create table t1_mc_p2 partition of t1_multi_col for values start (1, 300) end (100, 400);
ERROR: new partition's range overlaps with that of partition "t1_mc_p1" of "t1_multi_col"
HINT: Please specify a range that does not overlap with any existing partition's range.
That should be better realised using subpartitioning on b. The question is, if one column's value is enough to identify partition (since they can not contain overlapping values for that column), why do we need mutliple columns/expressions as partition keys? IIUC, all the other column does is to disallow certain range of values for that column, which can better be done by a CHECK constraint. It looks like Oracle looks at combined range and not just one column.
On Thu, Apr 21, 2016 at 7:35 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
Hi Ildar,
On 2016/04/21 1:06, Amit Langote wrote:
> On Wed, Apr 20, 2016 at 11:46 PM, Ildar Musin <i.musin@postgrespro.ru> wrote:
>> Crash occurs in get_check_expr_from_partbound(). It seems that function is
>> not yet expecting an expression key and designed to handle only simple
>> attributes keys. Backtrace:
>
> You're right, silly mistake. :-(
>
> Will fix
Attached updated version fixes this. I'll take time to send the next
version but I'd very much appreciate it if you keep reporting anything
that doesn't look/work right like you did so far.
Thanks,
Amit
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
With the new set of patches, I am getting following warnings but no compilation failures. Patches apply smoothly.
partition.c:1216:21: warning: variable ‘form’ set but not used [-Wunused-but-set-variable]
partition.c:1637:20: warning: variable ‘form’ set but not used [-Wunused-but-set-variable]
partition.c:1216:21: warning: variable ‘form’ set but not used [-Wunused-but-set-variable]
partition.c:1637:20: warning: variable ‘form’ set but not used [-Wunused-but-set-variable]
For creating partition the documentation seems to suggest the syntax
create table t1_p1 partition of t1 for values {start {0} end {100} exclusive;
create table t1_p1 partition of t1 for values {start {0} end {100} exclusive;
but following syntax works
create table t1_p1 partition of t1 for values start (0) end (100) exclusive;
create table t1_p1 partition of t1 for values start (0) end (100) exclusive;
> 5. In expand_partitioned_rtentry(), instead of finding all the leaf level
> relations, it might be better to build the RTEs, RelOptInfo and paths for
> intermediate relations as well. This helps in partition pruning. In your
> introductory write-up you have mentioned this. It might be better if v1
> includes this change, so that the partition hierarchy is visible in EXPLAIN
> output as well.
>
> 6. Explain output of scan on a partitioned table shows Append node with
> individual table scans as sub-plans. May be we should annotate Append node
> with
> the name of the partitioned table to make EXPLAIN output more readable.
I got rid of all the optimizer changes in the new version (except a line
or two). I did that by switching to storing parent-child relationships in
pg_inherits so that all the existing optimizer code for inheritance sets
works unchanged. Also, the implementation detail that required to put
only leaf partitions in the append list is also gone. Previously no
storage was allocated for partitioned tables (either root or any of the
internal partitions), so it was being done that way.
With this set of patches we are still flattening all the partitions.
postgres=# create table t1 (a int, b int) partition by range(a);
CREATE TABLE
postgres=# create table t1_p1 partition of t1 for values start (0) end (100) exclusive partition by range(b);
CREATE TABLE
postgres=# create table t1_p1_p1 partition of t1_p1 for values start (0) end (100) exclusive;
CREATE TABLE
postgres=# explain verbose select * from t1;
QUERY PLAN
-------------------------------------------------------------------------
Append (cost=0.00..32.60 rows=2262 width=8)
-> Seq Scan on public.t1 (cost=0.00..0.00 rows=1 width=8)
Output: t1.a, t1.b
-> Seq Scan on public.t1_p1 (cost=0.00..0.00 rows=1 width=8)
Output: t1_p1.a, t1_p1.b
-> Seq Scan on public.t1_p1_p1 (cost=0.00..32.60 rows=2260 width=8)
Output: t1_p1_p1.a, t1_p1_p1.b
(7 rows)
postgres=# create table t1 (a int, b int) partition by range(a);
CREATE TABLE
postgres=# create table t1_p1 partition of t1 for values start (0) end (100) exclusive partition by range(b);
CREATE TABLE
postgres=# create table t1_p1_p1 partition of t1_p1 for values start (0) end (100) exclusive;
CREATE TABLE
postgres=# explain verbose select * from t1;
QUERY PLAN
-------------------------------------------------------------------------
Append (cost=0.00..32.60 rows=2262 width=8)
-> Seq Scan on public.t1 (cost=0.00..0.00 rows=1 width=8)
Output: t1.a, t1.b
-> Seq Scan on public.t1_p1 (cost=0.00..0.00 rows=1 width=8)
Output: t1_p1.a, t1_p1.b
-> Seq Scan on public.t1_p1_p1 (cost=0.00..32.60 rows=2260 width=8)
Output: t1_p1_p1.a, t1_p1_p1.b
(7 rows)
Retaining the partition hierarchy would help to push-down join across partition hierarchy effectively.
Regarding 6, it seems to me that because Append does not have a associated
relid (like scan nodes have with scanrelid). Maybe we need to either fix
Append or create some enhanced version of Append which would also support
dynamic pruning.
Right, I think, Append might store the relid of the parent table as well as partitioning information at that level along-with the subplans.
Some more comments:
1. Would it be better to declare PartitionDescData as
{
int nparts;
PartitionInfo *partinfo; /* array of partition information structures. */
}
int nparts;
PartitionInfo *partinfo; /* array of partition information structures. */
}
2. The new syntax allows CREATE TABLE to be specified as partition of an already partitioned table. Is it possible to do the same for CREATE FOREIGN TABLE? Or that's material for v2? Similarly for ATTACH PARTITION.
3. PartitionKeyData contains KeyTypeCollInfo, whose contents can be obtained by calling functions exprType, exprTypemod on partexprs. Why do we need to store that information as a separate member?
3. PartitionKeyData contains KeyTypeCollInfo, whose contents can be obtained by calling functions exprType, exprTypemod on partexprs. Why do we need to store that information as a separate member?
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
Hi Amit,
I tried creating 2-level partitioned table and tried to create simple table using CTAS from the partitioned table. It gives a cache lookup error. Here's the testCREATE TABLE pt1_l (a int, b varchar, c int) PARTITION BY RANGE(a);
CREATE TABLE pt1_l_p1 PARTITION OF pt1_l FOR VALUES START (1) END (250) INCLUSIVE PARTITION BY RANGE(b);
CREATE TABLE pt1_l_p2 PARTITION OF pt1_l FOR VALUES START (251) END (500) INCLUSIVE PARTITION BY RANGE(((a+c)/2));
CREATE TABLE pt1_l_p3 PARTITION OF pt1_l FOR VALUES START (501) END (600) INCLUSIVE PARTITION BY RANGE(c);
CREATE TABLE pt1_l_p1_p1 PARTITION OF pt1_l_p1 FOR VALUES START ('000001') END ('000125') INCLUSIVE;
CREATE TABLE pt1_l_p1_p2 PARTITION OF pt1_l_p1 FOR VALUES START ('000126') END ('000250') INCLUSIVE;
CREATE TABLE pt1_l_p2_p1 PARTITION OF pt1_l_p2 FOR VALUES START (251) END (375) INCLUSIVE;
CREATE TABLE pt1_l_p2_p2 PARTITION OF pt1_l_p2 FOR VALUES START (376) END (500) INCLUSIVE;
CREATE TABLE pt1_l_p3_p1 PARTITION OF pt1_l_p3 FOR VALUES START (501) END (550) INCLUSIVE;
CREATE TABLE pt1_l_p3_p2 PARTITION OF pt1_l_p3 FOR VALUES START (551) END (600) INCLUSIVE;
INSERT INTO pt1_l SELECT i, to_char(i, 'FM000000'), i FROM generate_series(1, 600, 2) i;
CREATE TABLE upt1_l AS SELECT * FROM pt1_l;
On Thu, Jun 9, 2016 at 7:20 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
On 2016/06/08 22:22, Ashutosh Bapat wrote:
> On Mon, May 23, 2016 at 3:35 PM, Amit Langote wrote
>>
>> [...]
>>
>> I made a mistake in the last version of the patch which caused a relcache
>> field to be pfree'd unexpectedly. Attached updated patches.
>
> 0003-... patch does not apply cleanly. It has some conflicts in pg_dump.c.
> I have tried fixing the conflict in attached patch.
Thanks. See attached rebased patches.
Regards,
Amit
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
>> Regarding 6, it seems to me that because Append does not have a associated
>> relid (like scan nodes have with scanrelid). Maybe we need to either fix
>> Append or create some enhanced version of Append which would also support
>> dynamic pruning.
>>
>
> Right, I think, Append might store the relid of the parent table as well as
> partitioning information at that level along-with the subplans.
For time being, I will leave this as yet unaddressed (I am thinking about
what is reasonable to do for this also considering Robert's comment). Is
that OK?
Right now EXPLAIN of select * from t1, where t1 is partitioned table shows
Append
-> Seq Scan on t1
-> Seq scan on partition 1
-> seq scan on partition 2
...
...
which shows t1 as well as all the partitions on the same level. Users might have accepted that for inheritance hierarchy but for partitioning that can be confusing, esp. when all the error messages and documentation indicate that t1 is an empty (shell?) table. Instead showing it like
Append for t1 -- (essentially show that this is Append for partitioned table t1, exact text might vary)
-> Seq scan on partition 1
-> ....
-> ....
would be more readable. Similarly if we are going to collapse all the Append hierarchy, it might get even more confusing. Listing all the intermediate partitioned tables as Seq Scan on them would be confusing for the reasons mentioned above, and not listing them might make user wonder about the reasons for their disappearance. I am not sure what's the solution their.
> Some more comments
> Would it be better to declare PartitionDescData as
> {
> int nparts;
> PartitionInfo *partinfo; /* array of partition information structures. */
> }
I think that might be better. Will do it the way you suggest.
> The new syntax allows CREATE TABLE to be specified as partition of an
> already partitioned table. Is it possible to do the same for CREATE FOREIGN
> TABLE? Or that's material for v2? Similarly for ATTACH PARTITION.
OK, I will address this in the next version. One question though: should
foreign table be only allowed to be leaf partitions (ie, no PARTITION BY
clause in CREATE FOREIGN TABLE ... PARTITION OF)?
That seems a better way. Otherwise users might wonder whether we keep the partitions of a foreign table on the foreign server which won't be true. But then we allow foreign tables to have local tables as children in inheritance, so somebody from that background might find it incompatible. I think we shouldn't let the connection between partitioning and inheritance linger longer than necessary.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
Hi Amit,
I observed that the ChangeVarNodes call at line 1229 in get_relation_constraints() (code below) changes the varno in the cached copy of partition key expression, which is undesirable. The reason for this seems to be that the RelationGetPartitionCheckQual() returns the copy of partition key expression directly from the cache. This is mostly because get_check_qual_for_range() directly works on cached copy of partition key expressions, which it should never.1223 /* Append partition check quals, if any */
1224 pcqual = RelationGetPartitionCheckQual(relation);
1225 if (pcqual)
1226 {
1227 /* Fix Vars to have the desired varno */
1228 if (varno != 1)
1229 ChangeVarNodes((Node *) pcqual, 1, varno, 0);
1230
1231 result = list_concat(result, pcqual);
1232 }
On Mon, Jun 27, 2016 at 3:56 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
Hi Ashutosh,Thanks for the test case. I can reproduce the error. It has to do with
On 2016/06/24 23:08, Ashutosh Bapat wrote:
> Hi Amit,
> I tried creating 2-level partitioned table and tried to create simple table
> using CTAS from the partitioned table. It gives a cache lookup error.
> Here's the test
> CREATE TABLE pt1_l (a int, b varchar, c int) PARTITION BY RANGE(a);
> CREATE TABLE pt1_l_p1 PARTITION OF pt1_l FOR VALUES START (1) END (250)
> INCLUSIVE PARTITION BY RANGE(b);
> CREATE TABLE pt1_l_p2 PARTITION OF pt1_l FOR VALUES START (251) END (500)
> INCLUSIVE PARTITION BY RANGE(((a+c)/2));
> CREATE TABLE pt1_l_p3 PARTITION OF pt1_l FOR VALUES START (501) END (600)
> INCLUSIVE PARTITION BY RANGE(c);
> CREATE TABLE pt1_l_p1_p1 PARTITION OF pt1_l_p1 FOR VALUES START ('000001')
> END ('000125') INCLUSIVE;
> CREATE TABLE pt1_l_p1_p2 PARTITION OF pt1_l_p1 FOR VALUES START ('000126')
> END ('000250') INCLUSIVE;
> CREATE TABLE pt1_l_p2_p1 PARTITION OF pt1_l_p2 FOR VALUES START (251) END
> (375) INCLUSIVE;
> CREATE TABLE pt1_l_p2_p2 PARTITION OF pt1_l_p2 FOR VALUES START (376) END
> (500) INCLUSIVE;
> CREATE TABLE pt1_l_p3_p1 PARTITION OF pt1_l_p3 FOR VALUES START (501) END
> (550) INCLUSIVE;
> CREATE TABLE pt1_l_p3_p2 PARTITION OF pt1_l_p3 FOR VALUES START (551) END
> (600) INCLUSIVE;
> INSERT INTO pt1_l SELECT i, to_char(i, 'FM000000'), i FROM
> generate_series(1, 600, 2) i;
> CREATE TABLE upt1_l AS SELECT * FROM pt1_l;
>
> The last statement gives error "ERROR: cache lookup failed for function
> 0". Let me know if this problem is reproducible.
partition key column (b) being of type varchar whereas the default
operator family for the type being text_ops and there not being an
=(varchar, varchar) operator in text_ops.
When creating a plan for SELECT * FROM pt1_l, which is an Append plan,
partition check quals are generated internally to be used for constraint
exclusion - such as, b >= '000001' AND b < '000125'. Individual OpExpr's
are generated by using information from PartitionKey of the parent
(PartitionKey) and pg_partition entry of the partition. When choosing the
operator to use for =, >=, <, etc., opfamily and typid of corresponding
columns are referred. As mentioned above, in this case, they happened to
be text_ops and varchar, respectively, for column b. There doesn't exist
an operator =(varchar, varchar) in text_ops, so InvalidOid is returned by
get_opfamily_member which goes unchecked until the error in question occurs.
I have tried to fix this in attached updated patch by using operator class
input type for operator resolution in cases where column type didn't help.
Thanks,
Amit
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
What strikes me odd about these patches is RelOptInfo has remained unmodified. For a base partitioned table, I would expect it to be marked as partitioned may be indicating the partitioning scheme. Instead of that, I see that the code directly deals with PartitionDesc, PartitionKey which are part of Relation. It uses other structures like PartitionKeyExecInfo. I don't have any specific observation as to why we need such information in RelOptInfo, but lack of it makes me uncomfortable. It could be because inheritance code does not require any mark in RelOptInfo and your patch re-uses inheritance code. I am not sure.
On Tue, Aug 9, 2016 at 9:17 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
On 2016/08/09 6:02, Robert Haas wrote:
> On Mon, Aug 8, 2016 at 1:40 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> +1, if we could do it. It will need a change in the way Amit's patch stores
>>> partitioning scheme in PartitionDesc.
>>
>> Okay, I will try to implement this in the next version of the patch.
>>
>> One thing that comes to mind is what if a user wants to apply hash
>> operator class equality to list partitioned key by specifying a hash
>> operator class for the corresponding column. In that case, we would not
>> have the ordering procedure with an hash operator class, hence any
>> ordering based optimization becomes impossible to implement. The current
>> patch rejects a column for partition key if its type does not have a btree
>> operator class for both range and list methods, so this issue doesn't
>> exist, however it could be seen as a limitation.
>
> Yes, I think you should expect careful scrutiny of that issue. It
> seems clear to me that range partitioning requires a btree opclass,
> that hash partitioning requires a hash opclass, and that list
> partitioning requires at least one of those things. It would probably
> be reasonable to pick one or the other and insist that list
> partitioning always requires exactly that, but I can't see how it's
> reasonable to insist that you must have both types of opclass for any
> type of partitioning.
So because we intend to implement optimizations for list partition
metadata that presuppose existence of corresponding btree operator class,
we should just always require user to specify one (or error out if user
doesn't specify and a default one doesn't exist). That way, we explicitly
do not support specify hash equality operator for list partitioning.
>> So, we have 3 choices for the internal representation of list partitions:
>>
>> Choice 1 (the current approach): Load them in the same order as they are
>> found in the partition catalog:
>>
>> Table 1: p1 {'b', 'f'}, p2 {'c', 'd'}, p3 {'a', 'e'}
>> Table 2: p1 {'c', 'd'}, p2 {'a', 'e'}, p3 {'b', 'f'}
>>
>> In this case, mismatch on the first list would make the two tables
>> incompatibly partitioned, whereas they really aren't incompatible.
>
> Such a limitation seems clearly unacceptable. We absolutely must be
> able to match up compatible partitioning schemes without getting
> confused by little details like the order of the partitions.
Agreed. Will change my patch to adopt the below method.
>> Choice 2: Representation with 2 arrays:
>>
>> Table 1: ['a', 'b', 'c', 'd', 'e', 'f'], [3, 1, 2, 2, 3, 1]
>> Table 2: ['a', 'b', 'c', 'd', 'e', 'f'], [2, 3, 1, 1, 2, 3]
>>
>> It still doesn't help the case of pairwise joins because it's hard to tell
>> which value belongs to which partition (the 2nd array carries the original
>> partition numbers). Although it might still work for tuple-routing.
>
> It's very good for tuple routing. It can also be used to match up
> partitions for pairwise joins. Compare the first arrays. If they are
> unequal, stop. Else, compare the second arrays, incrementally
> building a mapping between them and returning false if the mapping
> turns out to be non-bijective. For example, in this case, we look at
> index 0 and decide that 3 -> 2. We look at index 1 and decide 1 -> 3.
> We look at index 2 and decide 2 -> 1. We look at index 4 and find
> that we already have a mapping for 2, but it's compatible because we
> need 2 -> 1 and that's what is already there. Similarly for the
> remaining entries. This is really a pretty easy loop to write and it
> should run very quickly.
I see, it does make sense to try to implement this way.
>> Choice 3: Order all lists' elements for each list individually and then
>> order the lists themselves on their first values:
>>
>> Table 1: p3 {'a', 'e'}, p2 {'b', 'f'}, p1 {'c', 'd'}
>> Table 2: p2 {'a', 'e'}, p1 {'b', 'f'}, p3 {'c', 'd'}
>>
>> This representation makes pairing partitions for pairwise joining
>> convenient but for tuple-routing we still need to visit each partition in
>> the worst case.
>
> I think this is clearly not good enough for tuple routing. If the
> algorithm I proposed above turns out to be too slow for matching
> partitions, then we could keep both this representation and the
> previous one. We are not limited to just one. But I don't think
> that's likely to be the case.
I agree. Let's see how the option 2 turns out.
> Also, note that all of this presupposes we're doing range
> partitioning, or perhaps list partitioning with a btree opclass. For
> partitioning based on a hash opclass, you'd organize the data based on
> the hash values rather than range comparisons.
Yes, the current patch does not implement hash partitioning, although I
have to think about how to support the hash case when designing the
internal data structures.
By the way, I am planning to start a new thread with the latest set of
patches which I will post in a day or two. I have tried to implement all
the bug fixes and improvements that have been suggested on this thread so
far. Thanks to all those who reviewed and gave their comments. Please
check this page to get a link to the new thread:
https://commitfest.postgresql.org/10/611/
Thanks,
Amit
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
On Fri, Aug 5, 2016 at 5:21 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Aug 5, 2016 at 6:53 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
> The lists for list partitioned tables are stored as they are specified by
> the user. While searching for a partition to route tuple to, we compare it
> with every list value of every partition. We might do something better
> similar to what's been done to range partitions. The list of values for a
> given partition can be stored in ascending/descending sorted order. Thus a
> binary search can be used to check whether given row's partition key column
> has same value as one in the list. The partitions can then be stored in the
> ascending/descending order of the least/greatest values of corresponding
> partitions.
+1. Here as with range partitions, we must be sure to know which
opclass should be used for the comparisons.
> We might be able to eliminate search in a given partition if its
> lowest value is higher than the given value or its higher value is lower
> than the given value.
I don't think I understand this part.
Consider lists ('e', 'i', 'f'), ('h', 'd','m') and ('l', 'b', 'a') for a list partitioned tables. I am suggesting that we arrange them as ('a','b','l'), ('d', 'h', 'm') and ('e', 'f', 'i'). If the given row (either for comparison or for inserting) has value 'c', we will search for it in ('a','b','l') but will be eliminate other two partitions since the second's partition's lowest value is higher than 'c' and lowest values of rest of the partitions are higher than that of the second partition. Without this order among the partitions, we will have to compare lowest values of all the partitions.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
0003-... patch does not apply cleanly. It has some conflicts in pg_dump.c. I have tried fixing the conflict in attached patch.
On Mon, May 23, 2016 at 3:35 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
Hi Ildar,
On 2016/05/21 0:29, Ildar Musin wrote:
> On 20.05.2016 11:37, Amit Langote wrote:
>> Moreover, instead of pruning partitions in planner prep phase, might it
>> not be better to do that when considering paths for the (partitioned) rel?
>> IOW, instead of looking at parse->jointree, we should rather be working
>> with rel->baserestrictinfo. Although, that would require some revisions
>> to how append_rel_list, simple_rel_list, etc. are constructed and
>> manipulated in a given planner invocation. Maybe it's time for that...
>> Again, you may have already considered these things.
>>
> Yes, you're right, this is how we did it in pg_pathman extension. But for
> this patch it requires further consideration and I'll do it in future!
OK, sure.
>> Could you try with the attached updated set of patches? I changed
>> partition descriptor relcache code to eliminate excessive copying in
>> previous versions.
[...]
> However I've encountered a problem which is that postgres crashes
> occasionally while creating partitions. Here is function that reproduces
> this behaviour:
>
> CREATE OR REPLACE FUNCTION fail()
> RETURNS void
> LANGUAGE plpgsql
> AS $$
> BEGIN
> DROP TABLE IF EXISTS abc CASCADE;
> CREATE TABLE abc (id SERIAL NOT NULL, a INT, dt TIMESTAMP) PARTITION BY
> RANGE (a);
> CREATE INDEX ON abc (a);
> CREATE TABLE abc_0 PARTITION OF abc FOR VALUES START (0) END (1000);
> CREATE TABLE abc_1 PARTITION OF abc FOR VALUES START (1000) END (2000);
> END
> $$;
>
> SELECT fail();
>
> It happens not every time but quite often. It doesn't happen if I execute
> this commands one by one in psql. Backtrace:
>
> #0 range_overlaps_existing_partition (key=0x7f1097504410,
> range_spec=0x1d0f400, pdesc=0x1d32200, with=0x7ffe437ead00) at
> partition.c:747
[...]
I made a mistake in the last version of the patch which caused a relcache
field to be pfree'd unexpectedly. Attached updated patches.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
IIUC, this seems like a combination of 2 and 3 above:
So, we have the following list partitions (as read from the catalog)
Table 1: p1 {'b', 'f'}, p2 {'c', 'd'}, p3 {'a', 'e'}
Table 2: p1 {'c', 'd'}, p2 {'a', 'e'}, p3 {'b', 'f'}
By applying the method of 3:
Table 1: p3 {'a', 'e'}, p2 {'b', 'f'}, p1 {'c', 'd'}
Table 2: p2 {'a', 'e'}, p1 {'b', 'f'}, p3 {'c', 'd'}
Then applying 2:
Table 1: ['a', 'b', 'c', 'd', 'e', 'f'], [1, 2, 3, 3, 1, 2], [3, 1, 2]
Table 2: ['a', 'b', 'c', 'd', 'e', 'f'], [1, 2, 3, 3, 1, 2], [2, 3, 1]
We should arrange the OID arrays to follow canonical order. Assuming that OIDs of p1, pt2, and p3 of table 1 are t1o1, t1o2 and t1o3 respectively, and those of p1, p2 and p3 of table 2 are t2o1, t2o2, t2o3 resp. the last arrays should be [t1o3, t1o2, t1o1] and [t2o2, t2o1, t2o3].Thus the last arrays from both representation give the OIDs of children that should be joined pair-wise. IOW, OID array should just follow the canonical order instead of specification order. AFAIU, your patch arranges the range partition OIDs in the canonical order and not specification order.
This is user-specification independent representation wherein the
partition numbers in the 2nd array are based on canonical representation
(ordered lists). To check pairwise join compatibility, simply compare the
first two arrays. As to which partitions (think OIDs, RTEs whatever) pair
with each other, simply pair corresponding elements of the 3rd array which
are original partitions numbers (or OIDs). Also when routing a tuple, we
find partition number in the array 2 and then look up the array 3 to get
the actual partition to insert the tuple.
Neither of these representations make the logic of checking pairwise-join
compatibility and pairing a subset of partitions (on either side) any
easier, although I haven't given it a good thought yet.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
With the new set of patches, I am getting following warnings but no compilation failures. Patches apply smoothly.
partition.c:1216:21: warning: variable ‘form’ set but not used [-Wunused-but-set-variable]
partition.c:1637:20: warning: variable ‘form’ set but not used [-Wunused-but-set-variable]
partition.c:1216:21: warning: variable ‘form’ set but not used [-Wunused-but-set-variable]
partition.c:1637:20: warning: variable ‘form’ set but not used [-Wunused-but-set-variable]
For creating partition the documentation seems to suggest the syntax
create table t1_p1 partition of t1 for values {start {0} end {100} exclusive;
create table t1_p1 partition of t1 for values {start {0} end {100} exclusive;
but following syntax works
create table t1_p1 partition of t1 for values start (0) end (100) exclusive;
create table t1_p1 partition of t1 for values start (0) end (100) exclusive;
> 5. In expand_partitioned_rtentry(), instead of finding all the leaf level
> relations, it might be better to build the RTEs, RelOptInfo and paths for
> intermediate relations as well. This helps in partition pruning. In your
> introductory write-up you have mentioned this. It might be better if v1
> includes this change, so that the partition hierarchy is visible in EXPLAIN
> output as well.
>
> 6. Explain output of scan on a partitioned table shows Append node with
> individual table scans as sub-plans. May be we should annotate Append node
> with
> the name of the partitioned table to make EXPLAIN output more readable.
I got rid of all the optimizer changes in the new version (except a line
or two). I did that by switching to storing parent-child relationships in
pg_inherits so that all the existing optimizer code for inheritance sets
works unchanged. Also, the implementation detail that required to put
only leaf partitions in the append list is also gone. Previously no
storage was allocated for partitioned tables (either root or any of the
internal partitions), so it was being done that way.
With this set of patches we are still flattening all the partitions.
postgres=# create table t1 (a int, b int) partition by range(a);
CREATE TABLE
postgres=# create table t1_p1 partition of t1 for values start (0) end (100) exclusive partition by range(b);
CREATE TABLE
postgres=# create table t1_p1_p1 partition of t1_p1 for values start (0) end (100) exclusive;
CREATE TABLE
postgres=# explain verbose select * from t1;
QUERY PLAN
-------------------------------------------------------------------------
Append (cost=0.00..32.60 rows=2262 width=8)
-> Seq Scan on public.t1 (cost=0.00..0.00 rows=1 width=8)
Output: t1.a, t1.b
-> Seq Scan on public.t1_p1 (cost=0.00..0.00 rows=1 width=8)
Output: t1_p1.a, t1_p1.b
-> Seq Scan on public.t1_p1_p1 (cost=0.00..32.60 rows=2260 width=8)
Output: t1_p1_p1.a, t1_p1_p1.b
(7 rows)
postgres=# create table t1 (a int, b int) partition by range(a);
CREATE TABLE
postgres=# create table t1_p1 partition of t1 for values start (0) end (100) exclusive partition by range(b);
CREATE TABLE
postgres=# create table t1_p1_p1 partition of t1_p1 for values start (0) end (100) exclusive;
CREATE TABLE
postgres=# explain verbose select * from t1;
QUERY PLAN
-------------------------------------------------------------------------
Append (cost=0.00..32.60 rows=2262 width=8)
-> Seq Scan on public.t1 (cost=0.00..0.00 rows=1 width=8)
Output: t1.a, t1.b
-> Seq Scan on public.t1_p1 (cost=0.00..0.00 rows=1 width=8)
Output: t1_p1.a, t1_p1.b
-> Seq Scan on public.t1_p1_p1 (cost=0.00..32.60 rows=2260 width=8)
Output: t1_p1_p1.a, t1_p1_p1.b
(7 rows)
Retaining the partition hierarchy would help to push-down join across partition hierarchy effectively.
Regarding 6, it seems to me that because Append does not have a associated
relid (like scan nodes have with scanrelid). Maybe we need to either fix
Append or create some enhanced version of Append which would also support
dynamic pruning.
Right, I think, Append might store the relid of the parent table as well as partitioning information at that level along-with the subplans.
Some more comments
Would it be better to declare PartitionDescData as
{
int nparts;
PartitionInfo *partinfo; /* array of partition information structures. */
}
int nparts;
PartitionInfo *partinfo; /* array of partition information structures. */
}
The new syntax allows CREATE TABLE to be specified as partition of an already partitioned table. Is it possible to do the same for CREATE FOREIGN TABLE? Or that's material for v2? Similarly for ATTACH PARTITION.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
Hi Amit,
Here are some random comments. You said that you were about to post a new patch, so you might have already taken care of those comments. But anyway here they are.1. Following patches do not apply cleanly for me.
0001
0003 - conflicts at #include for partition.h in rel.h.
0004 - conflicts in src/backend/catalog/Makefile
0005 - conflicts in src/backend/parser/gram.y
2. The patches are using now used OIDs 3318-3323. Corresponding objects need new unused oids.
3. In patch 0001-*, you are using indkey instead of partkey in one of the comments and also in documentation.
4. After all patches are applied I am getting several errors like "error: #error "lock.h may not be included from frontend code", while building rmgrdesc.c. This
seems to be because rel.h includes partition.h, which leads to inclusion of lock.h in rmgrdesc.c. Are you getting the same error message? It looks like we need separate header file for declaring function which can be used at the time of execution, which is anyway better irrespective of the compiler error.
5. In expand_partitioned_rtentry(), instead of finding all the leaf level relations, it might be better to build the RTEs, RelOptInfo and paths for
intermediate relations as well. This helps in partition pruning. In your introductory write-up you have mentioned this. It might be better if v1 includes this change, so that the partition hierarchy is visible in EXPLAIN output as well.
6. Explain output of scan on a partitioned table shows Append node with individual table scans as sub-plans. May be we should annotate Append node with
the name of the partitioned table to make EXPLAIN output more readable.
7. \d+ output of partitioned table does not show partitioning information, something necessary for V1.
8. Instead of storing partition key column numbers and expressions separately, can we store everything as expression; columns being a single Var node expression? That will avoid constructing Var nodes to lookup in equivalence classes for partition pruning or join optimizations.
10. The code distinguishes between the top level table and its partitions which in turn are partitioned. We should try to minimize this distinction as much as possible so as to use recursive functions for operating on partitions. E.g. each of the partitioned partitions may be labelled RELKIND_PARTITIONED_REL? A 0/NULL parent would distinguish between root partition and child partitions.
On Tue, Mar 22, 2016 at 7:53 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
On 2016/03/22 4:55, Robert Haas wrote:
> So, the last patch on this thread was posted on February 17th, and the
> CF entry was marked Waiting on Author on March 2nd. Even if we had a
> new patch in hand at this point, I don't think there's any real chance
> of being able to get this done for 9.6; there are too many things left
> to do here in terms of figuring out syntax and scope, and of course
> performance testing. Moreover, when this goes in, it's going to open
> up lots of opportunities for follow-up optimizations that we surely do
> not have time to follow up on for 9.6. And, as it is, the patch
> hasn't been updated in over a month and is clearly not in final form
> as it exists today.
>
> Therefore, I have marked this Returned with Feedback. I look forward
> to returning to this topic for 9.7, and I'm willing to step up to the
> plate and review this more aggressively at that time, with an eye
> toward committing it when we've got it in good shape. But I don't
> think there's any way to proceed with it for 9.6.
OK. I agree with the decision.
Actually, I was just about to post a patch set today, but I figure it's
too late for that. Anyway, I look forward to working on this for 9.7.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
I would think that for that case what we'd want to do is create two>
> Consider lists ('e', 'i', 'f'), ('h', 'd','m') and ('l', 'b', 'a') for a
> list partitioned tables. I am suggesting that we arrange them as
> ('a','b','l'), ('d', 'h', 'm') and ('e', 'f', 'i'). If the given row (either
> for comparison or for inserting) has value 'c', we will search for it in
> ('a','b','l') but will be eliminate other two partitions since the second's
> partition's lowest value is higher than 'c' and lowest values of rest of the
> partitions are higher than that of the second partition. Without this order
> among the partitions, we will have to compare lowest values of all the
> partitions.
lists. One looks like this:
[ 'a', 'b', 'd', 'e', f', 'h', 'i', 'l', 'm' ]
The other looks like this:
[3, 3, 2, 1, 1, 2, 1, 1, 3, 2]
small correction; there's an extra 1 here. Every partition in the example has only three values.
Given a particular value, bsearch the first list. If the value is not
found, it's not in any partition. If it is found, then go look up the
same array index in the second list; that's the containing partition.
+1, if we could do it. It will need a change in the way Amit's patch stores partitioning scheme in PartitionDesc.
This way specifications {('e', 'i', 'f'), ('h', 'd','m') and ('l', 'b', 'a')} and {('h', 'd','m') , ('e', 'i', 'f'), and ('l', 'b', 'a')} which are essentially same, are represented in different ways. It makes matching partitions for partition-wise join a bit tedius. We have to make sure that the first array matches for both the joining relations and then make sure that all the values belonging to the same partition for one table also belong to the same partition in the other table. Some more complex logic for matching subsets of lists for partition-wise join.
At least for straight forward partitioned table matching it helps to have both these array look same independent of the user specification. From that point of view, the partition be ordered by their lowest or highest list values and the second array is the index in the ordered set. For both the specifications above, the list will look like
[ 'a', 'b', 'd', 'e', f', 'h', 'i', 'l', 'm' ]
[1, 1, 2, 3, 3, 2, 3, 1, 2]
At least for straight forward partitioned table matching it helps to have both these array look same independent of the user specification. From that point of view, the partition be ordered by their lowest or highest list values and the second array is the index in the ordered set. For both the specifications above, the list will look like
[ 'a', 'b', 'd', 'e', f', 'h', 'i', 'l', 'm' ]
[1, 1, 2, 3, 3, 2, 3, 1, 2]
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
On Mon, Apr 18, 2016 at 1:23 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
On 2016/04/18 15:38, Ashutosh Bapat wrote:
>> There was no KeyTypeCollInfo in early days of the patch and then I found
>> myself doing a lot of:
>>
>> partexprs_item = list_head(key->partexprs);
>> for (attr in key->partattrs)
>> {
>> if (attr->attnum != 0)
>> {
>> // simple column reference, get type from attr
>> }
>> else
>> {
>> // expression, get type using exprType, etc.
>> partexprs_item = lnext(partexprs_item);
>> }
>> }
>>
>
> At least the two loops can be flattened to a single loop if we keep only
> expressions list with attributes being just Var nodes. exprType() etc.
> would then work seemlessly.
I didn't say anything about your suggestion to use a Node * list as a
representation for the cached partition key information. IIUC, you mean
instead of the AttrNumber[partnatts] array with non-zero attnum for a
named column slot and 0 for a expressional column slot, create a Node *
list with Var nodes for simple column references and Expr nodes for
expressions.
I would mention that the same information is also being used in contexts
where having simple attnums may be better (for example, when extracting
key of a tuple slot during tuple routing). Moreover, this is cached
information and I thought it may be better to follow the format that other
similar information uses (index key and such). Furthermore, looking at
qual matching code for indexes and recently introduced foreign key
optimization, it seems we will want to use a similar representation within
optimizer for partition keys. IndexOptInfo has int ncolumns and int *
indexkeys and then match_index_to_operand() compares index key attnums
with varattno of vars in qual. It's perhaps speculative at the moment
because there is not much code wanting to use it yet other than partition
DDL and tuple-routing and cached info seems to work as-is for the latter.
Ok.
>> That ended up being quite a few places (though I managed to reduce the
>> number of places over time). So, I created this struct which is
>> initialized when partition key is built (on first open of the partitioned
>> table).
>>
>
> Hmm, I am just afraid that we might end up with some code using cached
> information and some using exprType, exprTypmod etc.
Well, you never use exprType(), etc. for partition keys in other than a
few places. All places that do always use the cached values. Mostly
partitioning DDL stuff so far. Tuple routing considers collation of
individual key columns when comparing input value with partition bounds.
I am not worried about the current code. But there will be a lot of code added after version 1. I am worried about that.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Дата:
On 2016/04/15 18:46, Ashutosh Bapat wrote:
>
> 3. PartitionKeyData contains KeyTypeCollInfo, whose contents can be
> obtained by calling functions exprType, exprTypemod on partexprs. Why do we
> need to store that information as a separate member?
There was no KeyTypeCollInfo in early days of the patch and then I found
myself doing a lot of:
partexprs_item = list_head(key->partexprs);
for (attr in key->partattrs)
{
if (attr->attnum != 0)
{
// simple column reference, get type from attr
}
else
{
// expression, get type using exprType, etc.
partexprs_item = lnext(partexprs_item);
}
}
At least the two loops can be flattened to a single loop if we keep only expressions list with attributes being just Var nodes. exprType() etc. would then work seemlessly.
That ended up being quite a few places (though I managed to reduce the
number of places over time). So, I created this struct which is
initialized when partition key is built (on first open of the partitioned
table).
Hmm, I am just afraid that we might end up with some code using cached information and some using exprType, exprTypmod etc.
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
Re: Declarative partitioning
От:
Simon Riggs <simon@2ndQuadrant.com>
Дата:
On 18 August 2015 at 11:30, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
--
There is no need to define tuple routing triggers. CopyFrom() and
ExecInsert() determine target partition just before performing
heap_insert() and ExecInsertIndexTuples(). IOW, any BR triggers and
constraints (on parent) are executed for tuple before being routed to a
partition. If no partition can be found, it's an error.
Because row-level AFTER triggers need to save ItemPointers in trigger
event data and defining triggers on partitions (which is where tuples
really go) is not allowed, I could not find a straightforward way to
implement them. So, perhaps we should allow (only) row-level AFTER
triggers on partitions or think of modifying trigger.c to know about this
twist explicitly.
I think tables will eventually need FK support; its not sustainable as a long term restriction, though perhaps its something we can do in a later patch.
You haven't specified what would happen if an UPDATE would change a row's partition. I'm happy to add this to the list of restrictions by saying that the partition key cannot be updated.
We'll need regression tests that cover each restriction and docs that match. This is not something we should leave until last. People read the docs to understand the feature, helping them to reach consensus. So it is for you to provide the docs before, not wait until later. I will begin a code review once you tell me docs and tests are present. We all want the feature, so its all about the details now.
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: Declarative partitioning
От:
Simon Riggs <simon@2ndQuadrant.com>
Дата:
On 21 March 2016 at 19:55, Robert Haas <robertmhaas@gmail.com> wrote:
--
On Wed, Mar 16, 2016 at 10:49 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
>> > I'd like to validate that this development plan doesn't overlaps with
>> > your
>> > plans. If out plans are not overlapping then let's accept this plan of
>> > work
>> > for 9.7.
>>
>> It looks OK to me. Thanks for sharing it.
>
>
> Great! Let's work together.
So, the last patch on this thread was posted on February 17th, and the
CF entry was marked Waiting on Author on March 2nd. Even if we had a
new patch in hand at this point, I don't think there's any real chance
of being able to get this done for 9.6; there are too many things left
to do here in terms of figuring out syntax and scope, and of course
performance testing. Moreover, when this goes in, it's going to open
up lots of opportunities for follow-up optimizations that we surely do
not have time to follow up on for 9.6. And, as it is, the patch
hasn't been updated in over a month and is clearly not in final form
as it exists today.
Therefore, I have marked this Returned with Feedback. I look forward
to returning to this topic for 9.7, and I'm willing to step up to the
plate and review this more aggressively at that time, with an eye
toward committing it when we've got it in good shape. But I don't
think there's any way to proceed with it for 9.6.
Good decision.
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: Declarative partitioning
От:
Simon Riggs <simon@2ndQuadrant.com>
Дата:
On 20 November 2015 at 09:18, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
--
On 2015/11/06 1:29, Robert Haas wrote:
> On Fri, Oct 30, 2015 at 6:08 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> The DDL and catalogs part are not much different from what I had last
>> described though I took a few steps to simplify things. I dropped the
>> multi-level partitioning bit
>
> Hmm, that doesn't sound good to me. I think multi-level partitioning
> is a reasonably important use case.
I agree. I'm in the process of reformulating this proposal from the
syntax, catalog and DDL -centric perspective and will re-incorporate
multi-level partitioning notion into it. It was a mistake to drop it.
Drop it?? I think he means "in this initial patch", right Amit L ?
I don't really understand why parallel query was pursued in small pieces, but partitioning needs to happen all in one huge patch. Wishing too many things is going to slow down this feature.
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: Declarative partitioning
От:
Simon Riggs <simon@2ndQuadrant.com>
Дата:
On 24 August 2015 at 00:53, Josh Berkus <josh@agliodbs.com> wrote:
when a sequence of partitions has been defined
--
On 08/21/2015 08:34 PM, Jim Nasby wrote:
> On 8/18/15 12:31 PM, Josh Berkus wrote:
>> Also this would be useful for range
>> partitions:
>>
>> CREATE PARTITION ON parent_table USING ( start_value );
>>
>> ... where start_value is the start range of the new partition. Again,
>> easier for users to get correct.
>
> Instead of that, I think it would be more foolproof to do
>
> CREATE PARTITION ON parent_table FOR ( value1, ... );
>
> instead of trusting the user to get the exact start value correct.
>
> Though... I guess there could be value in allowing an exact start value
> but throwing an error if it doesn't sit exactly on a boundary. Might
> make it less likely to accidentally create the wrong partition.
Well, I'm figuring that most people would use "CREATE NEXT PARTITION"
instead.
ALTER TABLE foo ADD PARTITION NEXT;
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: Declarative partitioning
От:
Simon Riggs <simon@2ndQuadrant.com>
Дата:
On 4 September 2015 at 06:51, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
--
Sorry about the long delay in replying, to this message or the others
posted in the last few days. I should have notified in advance of my
vacation with rather limited Internet access.
No problem, I'm on leave too.
>> The patch does not yet implement any planner changes for partitioned
>> tables, although I'm working on the same and post updates as soon as
>> possible.
...
>
> This is really the heart of this patch/design. You can work for months on
> all the rest of this, but you will live or die by how the optimization
> works because that is the thing we really need to work well. Previous
> attempts ignored this aspect and didn't get committed. It's hard, perhaps
> even scary, but its critical. It's the 80/20 rule in reverse - 20% of the
> code is 80% of the difficulty.
>
> I suggest you write a partition query test script .sql and work towards
> making this work. Not exhaustive and weird tests, but 5-10 key queries that
> need to be optimized precisely and quickly. I'm sure that's been done
> before.
>
Yes, I am working on this and hope to have something to show soon.
No rush, no pressure; lets get this right.
>
> I couldn't see why you invented a new form of Alter Table recursion.
>
It was intended to keep the ALTER TABLE considerations for inherited
tables (and typed tables) separate from those for partitioned tables. But...
This begs a larger question that I did not try to answer in this
design/patch - for partitions, do we need to have any catalog entries
other than the pg_class tuple?
Everything should start from the requirements of the optimization approach. Once we have that clear, we can confirm other requirements.
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: Declarative partitioning
От:
Simon Riggs <simon@2ndQuadrant.com>
Дата:
On 18 August 2015 at 11:30, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
src/backend/access/nbtree/README
--
I would like propose $SUBJECT for this development cycle. Attached is a
WIP patch that implements most if not all of what's described below. Some
yet unaddressed parts are mentioned below, too. I'll add this to the CF-SEP.
Thanks for working on this. It's a great start.
3. Multi-level partitioning
CREATE TABLE table_name
PARTITION OF partitioned_table_name
FOR VALUES values_spec
PARTITION BY {RANGE|LIST} ON (columns_list)
This variant implements a form of so called composite or sub-partitioning
with arbitrarily deep partitioning structure. A table created using this
form has both the relkind RELKIND_PARTITIONED_REL and
pg_class.relispartition set to true.
Multi-level partitioning is probably going to complicate things beyond sanity.
One RELKIND_PARTITIONED_REL with lots of partitions sounds best to me. We can still have N dimensions of partitioning (or partitioning and subpartitioning, if you prefer that term)
The patch does not yet implement any planner changes for partitioned
tables, although I'm working on the same and post updates as soon as
possible. That means, it is not possible to run SELECT/UPDATE/DELETE
queries on partitioned tables without getting:
postgres=# SELECT * FROM persons;
ERROR: could not open file "base/13244/106975": No such file or directory
Given that there would be more direct ways of performing partition pruning
decisions with the proposed, it would be nice to utilize them.
Specifically, I would like to avoid having to rely on constraint exclusion
for partition pruning whereby subquery_planner() builds append_rel_list
and the later steps exclude useless partitions.
This is really the heart of this patch/design. You can work for months on all the rest of this, but you will live or die by how the optimization works because that is the thing we really need to work well. Previous attempts ignored this aspect and didn't get committed. It's hard, perhaps even scary, but its critical. It's the 80/20 rule in reverse - 20% of the code is 80% of the difficulty.
I suggest you write a partition query test script .sql and work towards making this work. Not exhaustive and weird tests, but 5-10 key queries that need to be optimized precisely and quickly. I'm sure that's been done before.
Will include the following once we start reaching consensus on main parts
of the proposed design/implementation:
* New regression tests
* Documentation updates
* pg_dump, psql, etc.
For reference, some immediately previous discussions:
* On partitioning *
http://www.postgresql.org/message-id/20140829155607.GF7705@eldon.alvh.no-ip.org
* Partitioning WIP patch *
http://www.postgresql.org/message-id/54EC32B6.9070605@lab.ntt.co.jp
If you want to achieve consensus, please write either docs or README files that explain how this works.
It took me a few seconds to notice deviations from Alvaro's original post. I shouldn't have to read a full thread to see what the conclusions were, you need to record them coherently.
Some great examples of such things are
src/backend/optimizer/READMEsrc/backend/access/nbtree/README
Please imagine how far such code would have got without them, then look at the code comments on the top of each of the functions in that area for examples of the clarity of design this needs.
Comments welcome!
Yes, comments in code are indeed welcome, as well as the README/docs.
I couldn't see why you invented a new form of Alter Table recursion.
We will need to support multi-row batched COPY.
I'm pleased to see this patch and will stay with it to completion, perhaps others also. We have 3 more CFs in this release, Nov, Jan, Mar - so this has a great chance of making it into 9.6. The current patch implements a bunch of stuff, but its hard to say what, how or why it does it and without the planner stuff its all moot. My recommendation is we say "Returned with Feedback" on this now, looking forward to next patch.
If you submit another patch before Nov, I will review it without waiting for Nov 1.
There will be much discussion on syntax, but that is not the key point. DDL Support routines are usually pretty straightforward too, so that can be left for now.
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: Declarative partitioning
От:
Simon Riggs <simon@2ndQuadrant.com>
Дата:
On 18 August 2015 at 18:31, Josh Berkus <josh@agliodbs.com> wrote:
--
> 2. Creating a partition of a partitioned table
>
> CREATE TABLE table_name
> PARTITION OF partitioned_table_name
> FOR VALUES values_spec;
>
> Where values_spec is:
>
> listvalues: [IN] (val1, ...)
>
> rangevalues: START (col1min, ... ) END (col1max, ... )
> | START (col1min, ... )
> | END (col1max, ... )
So, one thing I missed in here is anything about automated partitioning
of tables; that is, creating new partitions based on incoming data or a
simple statement which doesn't require knowledge of the partitioning
scheme. It's possible (and entirely accceptable) that you're
considering automated partition creation outside of the scope of this
patch.
I would like to make automatic partitioning outside the scope of this first patch.
However, for range partitions, it would be *really* useful to
have this syntax:
CREATE NEXT PARTITION ON parent_table;
Which would just create the "next" partition based on whatever the range
partitoning scheme is, instead of requiring the user to calculate start
and end values which might or might not match the parent partitioning
scheme, and might leave gaps. Also this would be useful for range
partitions:
CREATE PARTITION ON parent_table USING ( start_value );
... where start_value is the start range of the new partition. Again,
easier for users to get correct.
Both of these require the idea of regular intervals for range
partitions, that is, on a table partitioned by month on a timestamptz
column, each partition will have the range [ month:1, nextmonth:1 ).
This is the most common use-case for range partitions (like, 95% of all
partitioning cases I've seen), so a new partitioning scheme ought to
address it.
While there are certainly users who desire the ability to define
arbitrary ranges for each range partition, these are by far the minority
and could be accomodated by a different path with more complex syntax.
Further, I'd wager that most users who want to define arbitrary ranges
for range partitions aren't going to be satisfied with the other
restrictions on declarative partitioning (e.g. same constraints, columns
for all partitions) and are going to use inheritance partitioning anyway.
I like the idea of a regular partitioning step because it is how you design such tables - "lets use monthly partitions".
This gives sanely terse syntax, rather than specifying pages and pages of exact values in DDL....
PARTITION BY RANGE ON (columns) INCREMENT BY (INTERVAL '1 month' ) START WITH value;
borrowing the same concepts from sequence syntax.
> Creating index on parent is not allowed. They should be defined on (leaf)
> partitions. Because of this limitation, primary keys are not allowed on a
> partitioned table. Perhaps, we should be able to just create a dummy
> entry somewhere to represent an index on parent (which every partition
> then copies.)
This would be preferable, yes. Making users remember to manually create
indexes on each partition is undesirable.
I think it is useful to allow additional indexes on partitions, if desired, but we should always automatically build the indexes that are defined on the master when we create a new partition.
Presumably unique indexes will be allowed on partitions. So if the partition key is unique, we can say the whole partitioned table is unique and call that a Primary Key.
I would want individual partitions to be placed on separate tablespaces, but not by default.
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: Declarative partitioning
От:
Simon Riggs <simon@2ndQuadrant.com>
Дата:
On 20 August 2015 at 03:16, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
--
Sorry, should have added tests and docs already. I will add them in the
next version of the patch. Thanks for willing to review.
Thanks for picking up this challenge. It's easier if you have someone interested all the way.
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: Declarative partitioning
От:
Simon Riggs <simon@2ndQuadrant.com>
Дата:
On 20 August 2015 at 10:10, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
--
On 2015-08-20 AM 05:10, Josh Berkus wrote:
> On 08/19/2015 04:59 AM, Simon Riggs wrote:
>> I like the idea of a regular partitioning step because it is how you
>> design such tables - "lets use monthly partitions".
>>
>> This gives sanely terse syntax, rather than specifying pages and pages
>> of exact values in DDL....
>>
>> PARTITION BY RANGE ON (columns) INCREMENT BY (INTERVAL '1 month' )
>> START WITH value;
>
> Oh, I like that syntax!
>
> How would it work if there were multiple columns? Maybe we don't want
> to allow that for this form?
>
Yea, we could simply restrict it to the single column case, which does not
sound like a major restriction.
PARTITION BY ...
SUBPARTITION BY ...
We should plan for that in the way we develop the internals, but full support can wait until later patches.
My view has long been that the internals are they aspect here, not the syntax. We need to be able to have a very fast partition-selection mechanism that can be used in the planner or executor for each tuple. Working backwards, we need a relcache representation that allows that, and a catalog representation that allows that and syntax to match.
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Re: Declarative partitioning
От:
Alexander Korotkov <a.korotkov@postgrespro.ru>
Дата:
On Tue, Apr 19, 2016 at 4:57 PM, Ildar Musin <i.musin@postgrespro.ru> wrote:
On 15.04.2016 07:35, Amit Langote wrote:Thanks a lot for the comments. The patch set changed quite a bit since the last version. Once the CF entry was marked returned with feedback on March 22, I held off sending the new version at all. Perhaps, it would have been OK. Anyway here it is, if you are interested. I will create an entry in CF 2016-09 for the same. Also, see below replies to you individual comments.
Thanks for your new patch! I've tried it and discovered some strange behavior for partitioning by composite key. Here is an example of my setup:
create table test(a int, b int) partition by range (a, b);
create table test_1 partition of test for values start (0, 0) end (100, 100);
create table test_2 partition of test for values start (100, 100) end (200, 200);
create table test_3 partition of test for values start (200, 200) end (300, 300);
It's alright so far. But if we try to insert record in which attribute 'a' belongs to one partition and attribute 'b' belongs to another then record will be inserted in the first one:
insert into test(a, b) values (150, 50);
select tableoid::regclass, * from test;
tableoid | a | b
----------+-----+----
test_2 | 150 | 50
(1 row)
That's how composite keys work. First subkey is checked. If it's equal then second subkey is checked and so on.
# SELECT (100, 100) < (150, 50), (150, 50) < (200, 200);
?column? | ?column?
----------+----------
t | t
(1 row)
Another question is that it might be NOT what users expect from that. From the syntax side it very looks like defining something boxes regions for two keys which could be replacement for subpartitioning. But it isn't so.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Re: Declarative partitioning
От:
Alexander Korotkov <a.korotkov@postgrespro.ru>
Дата:
On Wed, Mar 16, 2016 at 5:29 PM, Amit Langote <amitlangote09@gmail.com> wrote:
> 9.6 feature freeze in coming, and we're planning our development resources
> for 9.7. Besides providing an extension, we would like these features to
> eventually arrive to core. In order to achieve this we need to port out
> functionality over your declarative syntax. At first, we would need some
> way for caching partition metadata suitable for fast partition selection.
> For range partition it could be sorted array or RB-tree of partition bounds.
> When we have this infrastructure, we can start porting pieces of pg_pathman
> functionality to declarative partitiong.
I had to think about the internal metadata representation (and its
caching) when developing the tuple routing solution. I am hopeful
that it is suitable for other executor mechanisms we will build for
partitioned tables.
Yes, it appears that I missed it. You already have sorted array for range partitioning and binary search implementation. This is good.
I'm a bit worrying about list partitioning because in this case you scan array sequentially. We could optimize this case for better handling of many list partitions. This is probably not most common case though.
> So, our draft plan of patches would be following:
>
> Implement partition metadata cache suitable for fast partition selection.
> Fast partition selection using metadata cache.
> Optimization of filter conditions passed to partitions.
> Execute time partitions selection (useful for nested loops and prepared
> statements);
> Optimization of ordered output from patitioned tables (use Append instead of
> MergeAppend when possible);
> Optimization of hash join when both tables are patitioned by join key.
>
> I'd like to validate that this development plan doesn't overlaps with your
> plans. If out plans are not overlapping then let's accept this plan of work
> for 9.7.
It looks OK to me. Thanks for sharing it.
Great! Let's work together.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Re: Declarative partitioning
От:
Alexander Korotkov <a.korotkov@postgrespro.ru>
Дата:
Hi, Amit!
I tried to apply your patch. It still applies, but has some duplicate oids. After fixing duplicate oids, I've noticed following warning during compilation by clang-700.1.81.
scan.c:10308:23: warning: unused variable 'yyg' [-Wunused-variable]
struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */
^
tablecmds.c:12922:6: warning: variable 'is_subpart' is used uninitialized whenever 'if' condition is false [-Wsometimes-uninitialized]
if (parent != NULL)
^~~~~~~~~~~~~~
tablecmds.c:12931:12: note: uninitialized use occurs here
partKey = is_subpart ? list_nth(rootParent->rd_partkeys, parent_level) :
^~~~~~~~~~
tablecmds.c:12922:2: note: remove the 'if' if its condition is always true
if (parent != NULL)
^~~~~~~~~~~~~~~~~~~
tablecmds.c:12912:19: note: initialize the variable 'is_subpart' to silence this warning
bool is_subpart;
^
= '\0'
tablecmds.c:13375:37: warning: variable 'operoid' is uninitialized when used here [-Wuninitialized]
comp_left_expr = make_opclause(operoid, BOOLOID, false,
^~~~~~~
tablecmds.c:13326:17: note: initialize the variable 'operoid' to silence this warning
Oid operoid;
^
= 0
Regression tests passed cleanly for me. I also examined code a bit. As I get, for DML queries, declarative partitioning works like inheritance. It just provides alternative way for collecting append_rel_list.
We're working on the other side of partitioning problem. Without solving syntax problem, we're solving performance problems in pg_pathman extension: https://github.com/postgrespro/pg_pathman. We already have interesting results which you can see in blog posts [1], [2], [3].
We already have fast algorithm for partition selection in optimizer [1] and effective optimization of filter conditions [3]. And we're planning to implement following features:
- Execute time partitions selection (useful for nested loops and prepared statements);
- Optimization of ordered output from patitioned tables (use Append instead of MergeAppend when possible);
- Optimization of hash join when both tables are patitioned by join key.
9.6 feature freeze in coming, and we're planning our development resources for 9.7. Besides providing an extension, we would like these features to eventually arrive to core. In order to achieve this we need to port out functionality over your declarative syntax. At first, we would need some way for caching partition metadata suitable for fast partition selection. For range partition it could be sorted array or RB-tree of partition bounds. When we have this infrastructure, we can start porting pieces of pg_pathman functionality to declarative partitiong.
So, our draft plan of patches would be following:
- Implement partition metadata cache suitable for fast partition selection.
- Fast partition selection using metadata cache.
- Optimization of filter conditions passed to partitions.
- Execute time partitions selection (useful for nested loops and prepared statements);
- Optimization of ordered output from patitioned tables (use Append instead of MergeAppend when possible);
- Optimization of hash join when both tables are patitioned by join key.
I'd like to validate that this development plan doesn't overlaps with your plans. If out plans are not overlapping then let's accept this plan of work for 9.7.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company