Performing ETL Using Inheritance in PostgreSQL

July 19, 2021
Author
Robert Bernier
Share this Post:

Good database maintenance includes not only standard operations such as adding, updating, and deleting records, but also periodic edits to the table schema. Operations such as adding, editing, and removing table columns are part of today’s lifecycle reality as new functionality is constantly added.

In quieter and less demanding times, one could get away with schema updates with minimal impact by performing the operation during low-load periods. However, as database systems have become more critical to the business bottom line, maintenance windows have become much smaller and more time-sensitive.

So the question is: how can one update a multi-terabyte table with near-zero downtime?

Looking deep into our Postgres bag of tricks, we look not at the most recent and advanced features, but instead leverage a very old capability that has been part of Postgres since it was first released as an open source project: its object-relational implementation of inheritance.

Use Case

Before going into the details of the solution, let’s define what we are trying to solve.

Strictly speaking, there are two use cases that come to mind when using inheritance as the primary ETL data migration mechanism:

  • Removing table OIDs, such as when moving to Postgres version 12 and later.
  • Performing DML/DDL operations, including:
    • Updating the data.
    • Adding or removing table columns.

Life starts getting complicated when dealing with issues such as updating data types. We will discuss this at the end of the blog.

About Inheritance

Unlike object-oriented programming languages, where the child inherits attributes from the parent, in Postgres the parent has the ability to inherit from the child. If only this were true in real life. Thus, a table column from a child has the potential to become available in the parent relation.

Consider the following snippet of code. Two parents and three children are created and populated with records:

Columns declared in the parents must exist in the child; they are merged. However, columns unique to the child are not necessarily propagated to the parents:

Even though records populated in the child can be seen by the parent, the reverse is not true. Records populated into the parent are not seen by the child:

Developing the ETL/Migration Model

Performing data migration under production conditions should take into consideration these four distinct query operations:

  • SELECT from both the target and source tables at the same time.
  • UPDATE and/or DELETE records from both the target and source tables.
  • INSERT new records into the target table.
  • Move data from the source table to the target table.

For the sake of discussion, we will demonstrate using one source table and one target table, each inheriting from a single parent:

Querying Both Target and Source Tables

Inheritance makes the SELECT query a straightforward operation. This query checks all tables for the queried records:

Update and/or Delete Records

Similar to SELECT queries, one does not have to edit the existing application’s DML operations when performing UPDATE and DELETE. Notice how both the source and target tables are queried, along with the parent:

Insert New Records Into the Target Table

The thing to keep in mind about INSERT is that, without a redirect mechanism, all records are inserted into the parent.

Since everyone already knows about triggers, I thought it would be fun to use a rewrite rule instead:

Here is our validation. Notice how the INSERT is redirected from parent to target:

Moving Records Between Source and Target Tables

It is time to introduce the last mechanism needed to perform the actual data migration. Essentially, the data is moved in batches. Otherwise, if you can afford the downtime of moving your records in one very large transaction, this dog-and-pony show is redundant.

In the real world, we need to anticipate multiple processes attempting simultaneous exclusive locks. If one or more records are locked by another operation, the following example demonstrates how you can simply skip over them:

Putting It All Together

It is time to demonstrate a proof of concept using pgbench.

Setup

Initialize database db02:

Create the tables parent and child.

Note: In order to demonstrate data migration from a deprecated table, table pgbench_accounts is altered by adding OIDs.

Test

This query is at the heart of the solution. Any exclusively locked record that it tries to move is automatically skipped, and a new attempt can be made the next time this script is invoked.

Validate

Migration Script

The query has been incorporated into this script, moving 1,000 records every five seconds.

Benchmarking

While the aforementioned script is active, pgbench is running the benchmarking.

Caveat

This is a simple and powerful method, but there are limitations. Common columns between tables must be of the same data type.

For example, if column c1 in table source is of data type int, and you want to migrate the data into table target with the same column c1 but with data type bigint, then this method will not work. An alternative solution could take advantage of updatable views, which you can read more about here, along with appropriate triggers and rewrite rules.

Percona Distribution for PostgreSQL provides the best and most critical enterprise components from the open-source community in a single distribution, designed and tested to work together.

Download Percona Distribution for PostgreSQL Today!

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments

Far
Enough.

Said no pioneer ever.
MySQL, PostgreSQL, InnoDB, MariaDB, MongoDB and Kubernetes are trademarks for their respective owners.
© 2026 Percona All Rights Reserved