Cool Stuff in PostgreSQL 10: Partitioned Audit Table
If you've dealt with PostgreSQL's answer to partitioning in the past, you may have put a lot of work into it. Happily, you have a lot less to bother with as of PostgreSQL 10, which is not out yet as of this writing.
Let's dive right in with a table that's a little like an audit trail.
CREATE TABLE IF NOT EXISTS the_log (
"timestamp" timestamp with time zone DEFAULT now() NOT NULL,
"user" text NOT NULL DEFAULT CURRENT_USER,
action text NOT NULL,
table_schema text NOT NULL,
table_name text NOT NULL,
old_row jsonb,
new_row jsonb,
CONSTRAINT the_log_check CHECK (
(old_row IS NOT NULL)::integer +
(new_row IS NOT NULL)::integer > 0
)
) PARTITION BY LIST(table_schema);
COMMENT ON CONSTRAINT the_log_check ON the_log IS
'Make sure at least one of the columns is NOT NULL';
A few things are going on here which are likely familiar to you if you're a PostgreSQL user:
A lot of hard work goes on in order to make this work, but now it's been amortized into the PostgreSQL software, where before it was externalized to you and me.
Since tables with a PARTITION clause can't actually store tuples, let's make a place to store them.
CREATE TABLE IF NOT EXISTS public_log
PARTITION OF the_log
FOR VALUES IN ('public')
PARTITION BY LIST(table_name);
Now we're ready to add some tuples, right? Not quite. There's a PARTITION BY at the end there, and that means this one can't hold tuples either. One more:
CREATE TABLE IF NOT EXISTS public_foo_log
PARTITION OF public_log
FOR VALUES IN ('foo');
No PARTITION clause, so this one should hold tuples, presumably logging INSERT, UPDATE, and DELETE on the foo table.
In the next installment, we'll use some new features to make those changes flow in.
Let's dive right in with a table that's a little like an audit trail.
CREATE TABLE IF NOT EXISTS the_log (
"timestamp" timestamp with time zone DEFAULT now() NOT NULL,
"user" text NOT NULL DEFAULT CURRENT_USER,
action text NOT NULL,
table_schema text NOT NULL,
table_name text NOT NULL,
old_row jsonb,
new_row jsonb,
CONSTRAINT the_log_check CHECK (
(old_row IS NOT NULL)::integer +
(new_row IS NOT NULL)::integer > 0
)
) PARTITION BY LIST(table_schema);
COMMENT ON CONSTRAINT the_log_check ON the_log IS
'Make sure at least one of the columns is NOT NULL';
A few things are going on here which are likely familiar to you if you're a PostgreSQL user:
- CREATE ... IF NOT EXISTS, which lets you move toward those single-transaction deployments that have made PostgreSQL justly famous
- Smart defaults on columns
- JSONB because you'll probably want to probe inside a structure that might change over time
- A CHECK constraint that ensures at least one not-NULL among the columns it looks at. This can be generalized in a few different directions. (This one's not actually all that common, but it really should be.)
A lot of hard work goes on in order to make this work, but now it's been amortized into the PostgreSQL software, where before it was externalized to you and me.
Since tables with a PARTITION clause can't actually store tuples, let's make a place to store them.
CREATE TABLE IF NOT EXISTS public_log
PARTITION OF the_log
FOR VALUES IN ('public')
PARTITION BY LIST(table_name);
Now we're ready to add some tuples, right? Not quite. There's a PARTITION BY at the end there, and that means this one can't hold tuples either. One more:
CREATE TABLE IF NOT EXISTS public_foo_log
PARTITION OF public_log
FOR VALUES IN ('foo');
No PARTITION clause, so this one should hold tuples, presumably logging INSERT, UPDATE, and DELETE on the foo table.
In the next installment, we'll use some new features to make those changes flow in.
Cool article... I think you can simplify the check constraint in "the_log" table using this instead:
ReplyDelete...
CONSTRAINT the_log_check CHECK (old_row IS DISTINCT FROM new_row)
...
That constraint is not precisely identical, as it would disallow identity updates, which can sometimes happen and aren't necessarily errors.
DeleteAlso, it's hard to generalize to >2 expressions.