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:

  • 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.)
One thing is really new: PARTITION.  In this case, we're partitioning the overarching table by one or more values in the table_schema column.  We're using LIST rather than RANGE because while schemas can definitely be sorted, it's probably not something important that you might use in a query.  LIST partitions are meant to highlight the uniqueness of (a few) individuals.

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.

Comments

  1. Cool article... I think you can simplify the check constraint in "the_log" table using this instead:

    ...
    CONSTRAINT the_log_check CHECK (old_row IS DISTINCT FROM new_row)
    ...

    ReplyDelete
    Replies
    1. That constraint is not precisely identical, as it would disallow identity updates, which can sometimes happen and aren't necessarily errors.

      Also, it's hard to generalize to >2 expressions.

      Delete

Post a Comment

Popular posts from this blog

Cool Stuff in PostgreSQL 10: Transition Table Triggers

Cool Stuff in PostgreSQL 10: Auto-logging

You don't need PL/pgsql!