ALTER TABLE ... REPLACE WITH

Started by Simon Riggsover 15 years ago63 messageshackers
Jump to latest
#1Simon Riggs
simon@2ndQuadrant.com

There are various applications where we want to completely replace the
contents of a table with new/re-calculated data.

It seems fairly obvious to be able to do this like...
1. Prepare new data into "new_table" and build indexes
2. Swap old for new
BEGIN;
DROP TABLE "old_table";
ALTER TABLE "new_table" RENAME to "old_table";
COMMIT;

Step (2) works, but any people queuing to access the table will see
ERROR: could not open relation with OID xxxxx
What we need is a way to atomically replace the contents of a table
without receiving this error. (You can't use views).

What I propose is to write a function/command to allow this to be
explicitly achievable by the server.

ALTER TABLE "old_table"
REPLACE WITH "new_table";

This would do the following:
* Check that *content* definitions of old and new are the same
* Drop all old indexes
* Move new relfilenode into place
* Move all indexes from new to old (the set of indexes may change)
* All triggers, non-index constraints, defaults etc would remain same
* "new_table" is TRUNCATEd.

TRUNCATE already achieves something similar, and is equivalent to
REPLACE WITH an empty table, so we know it is possible. Obviously this
breaks MVCC, but the applications for this don't care.

Of course, as with all things, this can be done with a function and some
dodgy catalog updates. I'd rather avoid that and have this as a full
strength capability on the server, since it has a very wide range of
potential applications of use to all Postgres users.

Similar, though not inspired by EXCHANGE PARTITION in Oracle.

It looks a short project to me, just some checks and a few updates.

Objections?

--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Simon Riggs (#1)
Re: ALTER TABLE ... REPLACE WITH

Simon Riggs <simon@2ndQuadrant.com> writes:

There are various applications where we want to completely replace the
contents of a table with new/re-calculated data.

It seems fairly obvious to be able to do this like...
1. Prepare new data into "new_table" and build indexes
2. Swap old for new
BEGIN;
DROP TABLE "old_table";
ALTER TABLE "new_table" RENAME to "old_table";
COMMIT;

Why not

BEGIN;
TRUNCATE TABLE;
... load new data ...
COMMIT;

What I propose is to write a function/command to allow this to be
explicitly achievable by the server.

ALTER TABLE "old_table"
REPLACE WITH "new_table";

I don't think the cost/benefit ratio of this is anywhere near as good
as you seem to think (ie, you're both underestimating the work involved
and overstating the benefit). I'm also noticing a lack of specification
as to trigger behavior, foreign keys, etc. The apparent intention to
disregard FKs entirely is particularly distressing,

regards, tom lane

#3Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#2)
Re: ALTER TABLE ... REPLACE WITH

On Tue, 2010-12-14 at 13:54 -0500, Tom Lane wrote:

Simon Riggs <simon@2ndQuadrant.com> writes:

There are various applications where we want to completely replace the
contents of a table with new/re-calculated data.

It seems fairly obvious to be able to do this like...
1. Prepare new data into "new_table" and build indexes
2. Swap old for new
BEGIN;
DROP TABLE "old_table";
ALTER TABLE "new_table" RENAME to "old_table";
COMMIT;

Why not

BEGIN;
TRUNCATE TABLE;
... load new data ...
COMMIT;

The above is atomic, but not fast.

The intention is to produce an atomic swap with as small a lock window
as possible, to allow it to happen in real operational systems.

At the moment we have a choice of fast or atomic. We need both.

(Note that there are 2 utilities that already do this, but the
operations aren't supported in core Postgres).

What I propose is to write a function/command to allow this to be
explicitly achievable by the server.

ALTER TABLE "old_table"
REPLACE WITH "new_table";

I don't think the cost/benefit ratio of this is anywhere near as good
as you seem to think (ie, you're both underestimating the work involved
and overstating the benefit). I'm also noticing a lack of specification
as to trigger behavior, foreign keys, etc. The apparent intention to
disregard FKs entirely is particularly distressing,

No triggers would be fired. All constraints that exist on "old_table"
must also exist on "new_table". As I said, lots of checks required, no
intention to add back doors.

("Disregard FKs" is the other project, not connected other than both are
operations on tables designed to improve manageability of large tables.)

--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services

#4Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#2)
Re: ALTER TABLE ... REPLACE WITH

On Tue, Dec 14, 2010 at 1:54 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

BEGIN;
TRUNCATE TABLE;
... load new data ...
COMMIT;

Because then you have to take an AccessExclusiveLock on the target
table, of course.

If we had some kind of TRUNCATE CONCURRENTLY, I think that'd address a
large portion of the use case for the proposed feature.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#5Josh Berkus
josh@agliodbs.com
In reply to: Robert Haas (#4)
Re: ALTER TABLE ... REPLACE WITH

On 12/14/10 11:07 AM, Robert Haas wrote:

Because then you have to take an AccessExclusiveLock on the target
table, of course.

Well, you have to do that for DROP TABLE as well, and I don't see any
way around doing it for REPLACE WITH.

As for the utility of this command: there is no question that I would
use it. I'm not sure I like the syntax (I'd prefer REPLACE TABLE ____
WITH _____), but that's painting the bike shed. While the command may
appear frivolous and unnecessary syntactical ornamentation to some, I
have to say that doing the "table doesy-doe" which this command
addresses is something I have written scripts for on at least 50% of my
professional clients. It keeps coming up.

In order for REPLACE WITH to be really useful, though, we need a command
cloning at table design with *all* constraints, FKs, keys, and indexes.
Currently, I still don't think we have that ... do we?

--
-- Josh Berkus
PostgreSQL Experts Inc.
http://www.pgexperts.com

#6Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Simon Riggs (#1)
Re: ALTER TABLE ... REPLACE WITH

On 14.12.2010 20:27, Simon Riggs wrote:

There are various applications where we want to completely replace the
contents of a table with new/re-calculated data.

It seems fairly obvious to be able to do this like...
1. Prepare new data into "new_table" and build indexes
2. Swap old for new
BEGIN;
DROP TABLE "old_table";
ALTER TABLE "new_table" RENAME to "old_table";
COMMIT;

Step (2) works, but any people queuing to access the table will see
ERROR: could not open relation with OID xxxxx

Could we make that work without error?

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#7Robert Haas
robertmhaas@gmail.com
In reply to: Josh Berkus (#5)
Re: ALTER TABLE ... REPLACE WITH

On Tue, Dec 14, 2010 at 2:34 PM, Josh Berkus <josh@agliodbs.com> wrote:

On 12/14/10 11:07 AM, Robert Haas wrote:

Because then you have to take an AccessExclusiveLock on the target
table, of course.

Well, you have to do that for DROP TABLE as well, and I don't see any
way around doing it for REPLACE WITH.

Sure, but in Simon's proposal you can load the data FIRST and then
take a lock just long enough to do the swap. That's very different
from needing to hold the lock during the whole data load.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#8Simon Riggs
simon@2ndQuadrant.com
In reply to: Heikki Linnakangas (#6)
Re: ALTER TABLE ... REPLACE WITH

On Tue, 2010-12-14 at 21:35 +0200, Heikki Linnakangas wrote:

On 14.12.2010 20:27, Simon Riggs wrote:

There are various applications where we want to completely replace the
contents of a table with new/re-calculated data.

It seems fairly obvious to be able to do this like...
1. Prepare new data into "new_table" and build indexes
2. Swap old for new
BEGIN;
DROP TABLE "old_table";
ALTER TABLE "new_table" RENAME to "old_table";
COMMIT;

Step (2) works, but any people queuing to access the table will see
ERROR: could not open relation with OID xxxxx

Could we make that work without error?

Possibly, and good thinking, but its effectively the same patch, just
syntax free since we still need to do lots of checking to avoid swapping
oranges with lemons.

I prefer explicit syntax because its easier to be certain that you've
got it right.

--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services

#9Simon Riggs
simon@2ndQuadrant.com
In reply to: Josh Berkus (#5)
Re: ALTER TABLE ... REPLACE WITH

On Tue, 2010-12-14 at 11:34 -0800, Josh Berkus wrote:

In order for REPLACE WITH to be really useful, though, we need a
command cloning at table design with *all* constraints, FKs, keys, and
indexes. Currently, I still don't think we have that ... do we?

Being able to vary the indexes when we REPLACE is a good feature.

We only need to check that datatypes and constraints match.

--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services

#10Simon Riggs
simon@2ndQuadrant.com
In reply to: Josh Berkus (#5)
Re: ALTER TABLE ... REPLACE WITH

On Tue, 2010-12-14 at 11:34 -0800, Josh Berkus wrote:

As for the utility of this command: there is no question that I would
use it. I'm not sure I like the syntax (I'd prefer REPLACE TABLE ____
WITH _____), but that's painting the bike shed.

REPLACE TABLE ying WITH yang

is probably easier to implement than hacking at the ALTER TABLE code
mountain.

While the command may
appear frivolous and unnecessary syntactical ornamentation to some, I
have to say that doing the "table doesy-doe" which this command
addresses is something I have written scripts for on at least 50% of
my professional clients. It keeps coming up.

Yeh.

--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services

#11Josh Berkus
josh@agliodbs.com
In reply to: Simon Riggs (#9)
Re: ALTER TABLE ... REPLACE WITH

On 12/14/10 11:43 AM, Simon Riggs wrote:

On Tue, 2010-12-14 at 11:34 -0800, Josh Berkus wrote:

In order for REPLACE WITH to be really useful, though, we need a
command cloning at table design with *all* constraints, FKs, keys, and
indexes. Currently, I still don't think we have that ... do we?

Being able to vary the indexes when we REPLACE is a good feature.

We only need to check that datatypes and constraints match.

No, you're missing my point ... currently we don't have a command which
says "make an identical clone of this table". CREATE TABLE AS allows us
to copy all of the data for the table, but not the full table design.
CREATE TABLE LIKE gives us most of the design (although it still won't
copy FKs) but won't copy the data.

However, for the usual do-si-do case, you need to populate the data
using a query and not clone all the data. What you'd really need is
something like:

CREATE TABLE new_table LIKE old_table ( INCLUDING ALL ) FROM SELECT ...

.. which would create the base tabledef, copy in the data from the
query, and then apply all the constraints, indexes, defaults, etc.

Without some means of doing a clone of the table in a single command,
you've eliminated half the scripting work, but not helped at all with
the other half.

Actually, you know what would be ideal?

REPLACE TABLE old_table WITH SELECT ...

Give it some thought ...

--
-- Josh Berkus
PostgreSQL Experts Inc.
http://www.pgexperts.com

#12Simon Riggs
simon@2ndQuadrant.com
In reply to: Josh Berkus (#11)
Re: ALTER TABLE ... REPLACE WITH

On Tue, 2010-12-14 at 16:19 -0800, Josh Berkus wrote:

Without some means of doing a clone of the table in a single command,
you've eliminated half the scripting work, but not helped at all with
the other half.

I'm not trying to eliminate scripting work, I'm trying to minimise the
lock window with a reliable and smooth atomic switcheroo.

Actually, you know what would be ideal?

REPLACE TABLE old_table WITH SELECT ...

Give it some thought ...

I have; the above would hold the lock window open while the SELECT runs
and that is explicitly something we are trying to avoid.

Good creative input though, thank you.

--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services

#13Josh Berkus
josh@agliodbs.com
In reply to: Simon Riggs (#12)
Re: ALTER TABLE ... REPLACE WITH

I have; the above would hold the lock window open while the SELECT runs
and that is explicitly something we are trying to avoid.

Not necessarily. You could copy into a temp table first, and then swap.

--
-- Josh Berkus
PostgreSQL Experts Inc.
http://www.pgexperts.com

#14Csaba Nagy
ncslists@googlemail.com
In reply to: Robert Haas (#7)
Re: ALTER TABLE ... REPLACE WITH

On Tue, 2010-12-14 at 14:36 -0500, Robert Haas wrote:

Well, you have to do that for DROP TABLE as well, and I don't see any
way around doing it for REPLACE WITH.

Sure, but in Simon's proposal you can load the data FIRST and then
take a lock just long enough to do the swap. That's very different
from needing to hold the lock during the whole data load.

Except Simon's original proposal has this line in it:

* "new_table" is TRUNCATEd.

I guess Simon mixed up "new_table" and "old_table", and the one which
should get truncated is the replaced one and not the replacement,
otherwise it doesn't make sense to me.

BTW, I would have also used such a feature on multiple occasions in the
past and expect I would do in the future too.

Cheers,
Csaba.

#15Simon Riggs
simon@2ndQuadrant.com
In reply to: Csaba Nagy (#14)
Re: ALTER TABLE ... REPLACE WITH

On Wed, 2010-12-15 at 10:54 +0100, Csaba Nagy wrote:

On Tue, 2010-12-14 at 14:36 -0500, Robert Haas wrote:

Well, you have to do that for DROP TABLE as well, and I don't see any
way around doing it for REPLACE WITH.

Sure, but in Simon's proposal you can load the data FIRST and then
take a lock just long enough to do the swap. That's very different
from needing to hold the lock during the whole data load.

Except Simon's original proposal has this line in it:

* "new_table" is TRUNCATEd.

I guess Simon mixed up "new_table" and "old_table", and the one which
should get truncated is the replaced one and not the replacement,
otherwise it doesn't make sense to me.

What I meant was...

REPLACE TABLE target WITH source;

* target's old rows are discarded
* target's new rows are all of the rows from "source".
* source is then truncated, so ends up empty

Perhaps a more useful definition would be

EXCHANGE TABLE target WITH source;

which just swaps the heap and indexes of each table.
You can then use TRUNCATE if you want to actually destroy data.

I will go with that unless we have other objections.

BTW, I would have also used such a feature on multiple occasions in the
past and expect I would do in the future too.

Cheers,
Csaba.

--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services

#16Csaba Nagy
ncslists@googlemail.com
In reply to: Simon Riggs (#15)
Re: ALTER TABLE ... REPLACE WITH

On Wed, 2010-12-15 at 10:39 +0000, Simon Riggs wrote:

Perhaps a more useful definition would be

EXCHANGE TABLE target WITH source;

which just swaps the heap and indexes of each table.
You can then use TRUNCATE if you want to actually destroy data.

Yes please, that's exactly what I would have needed in many occasions.

But one problem would be when the replaced table is the _parent_ for a
foreign key relationship. I don't think you can have that constraint
pre-verified on the replacement table and simply replacing the content
could leave the child relations with orphans.

Cheers,
Csaba.

#17Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#15)
Re: ALTER TABLE ... REPLACE WITH

On Wed, Dec 15, 2010 at 5:39 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

Perhaps a more useful definition would be

EXCHANGE TABLE target WITH source;

which just swaps the heap and indexes of each table.
You can then use TRUNCATE if you want to actually destroy data.

I will go with that unless we have other objections.

I still don't see how that's going to work with foreign keys. If
there's a foreign key referencing the old table, there's no way to be
sure that all of those references are still going to be valid with
respect to the new table without a full-table check. And that seems
to defeat the purpose of the feature.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#18David Christensen
david@endpoint.com
In reply to: Simon Riggs (#15)
Re: ALTER TABLE ... REPLACE WITH

On Dec 15, 2010, at 4:39 AM, Simon Riggs wrote:

On Wed, 2010-12-15 at 10:54 +0100, Csaba Nagy wrote:

On Tue, 2010-12-14 at 14:36 -0500, Robert Haas wrote:

Well, you have to do that for DROP TABLE as well, and I don't see any
way around doing it for REPLACE WITH.

Sure, but in Simon's proposal you can load the data FIRST and then
take a lock just long enough to do the swap. That's very different
from needing to hold the lock during the whole data load.

Except Simon's original proposal has this line in it:

* "new_table" is TRUNCATEd.

I guess Simon mixed up "new_table" and "old_table", and the one which
should get truncated is the replaced one and not the replacement,
otherwise it doesn't make sense to me.

What I meant was...

REPLACE TABLE target WITH source;

* target's old rows are discarded
* target's new rows are all of the rows from "source".
* source is then truncated, so ends up empty

Perhaps a more useful definition would be

EXCHANGE TABLE target WITH source;

which just swaps the heap and indexes of each table.
You can then use TRUNCATE if you want to actually destroy data.

Are there any considerations with toast tables and the inline line pointers for toasted tuples?

Regards,

David
--
David Christensen
End Point Corporation
david@endpoint.com

#19Simon Riggs
simon@2ndQuadrant.com
In reply to: David Christensen (#18)
Re: ALTER TABLE ... REPLACE WITH

On Wed, 2010-12-15 at 07:43 -0600, David Christensen wrote:

Are there any considerations with toast tables and the inline line pointers for toasted tuples?

Toast tables would be swapped as well. Toast pointers are only
applicable within a relfilenode, so we could not do otherwise.

--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services

#20Simon Riggs
simon@2ndQuadrant.com
In reply to: Csaba Nagy (#16)
Re: ALTER TABLE ... REPLACE WITH

On Wed, 2010-12-15 at 12:17 +0100, Csaba Nagy wrote:

But one problem would be when the replaced table is the _parent_ for a
foreign key relationship. I don't think you can have that constraint
pre-verified on the replacement table and simply replacing the content
could leave the child relations with orphans.

Good point.

The only sensible way to handle this is by putting the FK checks into
check pending state (as discussed on a different thread).

We would probably need to disallow FKs with DELETE or UPDATE CASCADE
since it would be difficult to execute those.

--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services

#21Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#20)
#22Kevin Grittner
Kevin.Grittner@wicourts.gov
In reply to: Robert Haas (#21)
#23Robert Haas
robertmhaas@gmail.com
In reply to: Kevin Grittner (#22)
#24bricklen
bricklen@gmail.com
In reply to: Simon Riggs (#15)
#25Simon Riggs
simon@2ndQuadrant.com
In reply to: bricklen (#24)
#26Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#10)
#27Noah Misch
noah@leadboat.com
In reply to: Simon Riggs (#26)
#28Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#27)
#29Noah Misch
noah@leadboat.com
In reply to: Simon Riggs (#28)
#30Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#28)
#31Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#30)
#32Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#31)
#33Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#32)
#34Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#33)
#35Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#27)
#36Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#34)
#37Noah Misch
noah@leadboat.com
In reply to: Simon Riggs (#35)
#38Tom Lane
tgl@sss.pgh.pa.us
In reply to: Noah Misch (#37)
#39Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#37)
#40Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#38)
#41Noah Misch
noah@leadboat.com
In reply to: Simon Riggs (#39)
#42Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#40)
#43Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#36)
#44Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#41)
#45Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#39)
#46Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#44)
#47Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#46)
#48Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#47)
#49Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#48)
#50Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#49)
#51Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#50)
#52Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#51)
#53Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#52)
#54Bruce Momjian
bruce@momjian.us
In reply to: Noah Misch (#46)
#55Noah Misch
noah@leadboat.com
In reply to: Bruce Momjian (#54)
#56Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#55)
#57Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#56)
#58Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#57)
#59Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#58)
#60Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#59)
#61Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#60)
#62Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#61)
#63Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#62)