- Denish Patel
- 9th July 2018
- auditing, Database, postgresql
As I mentioned in my previous post “Audit logging using JSONB in Postgres” , audit tables can be partitioned easily in Postgres 10.
Let’s use Postgres partitioning in sample users_audit table…
Drop existing trigger on live table and users_audit table
drop trigger users_audit_trig ON public.users; drop table if exists audit.users_audit;
Create partition table by RANGE partition on audit_ts timestamp column …
set search_path to audit; create table audit.users_audit( audit_ts timestamptz not null default now(), operation varchar(10)not null, username text not null default "current_user"(), before jsonb, after jsonb ) partition by RANGE (audit_ts);
Create child tables…
CREATE TABLE audit.users_audit_2018_07 PARTITION OF audit.users_audit FOR VALUES FROM ('2018-07-01') TO ('2018-08-01'); CREATE TABLE audit.users_audit_2018_08 PARTITION OF audit.users_audit FOR VALUES FROM ('2018-08-01') TO ('2018-09-01'); CREATE TABLE audit.users_audit_2018_09 PARTITION OF audit.users_audit FOR VALUES FROM ('2018-09-01') TO ('2018-10-01'); CREATE TABLE audit.users_audit_2018_10 PARTITION OF audit.users_audit FOR VALUES FROM ('2018-10-01') TO ('2018-11-01'); CREATE TABLE audit.users_audit_2018_11 PARTITION OF audit.users_audit FOR VALUES FROM ('2018-11-01') TO ('2018-12-01'); CREATE TABLE audit.users_audit_2018_12 PARTITION OF audit.users_audit FOR VALUES FROM ('2018-12-01') TO ('2019-01-01');
Create required index on EACH partitioned table..
create index on audit.users_audit_2018_07 (audit_ts desc,operation); create index on audit.users_audit_2018_07 using GIN(before); create index on audit.users_audit_2018_07 using GIN(after); create index on audit.users_audit_2018_07 using GIN ((after->'userid'));
NOTE: you have to pre-create these child tables as well indices in advance so you can come up with process to create them using some kind of script or add trigger on parent partitioned table to create child table automatically.
Place the trigger back on USERS table..
CREATE TRIGGER users_audit_trig BEFORE INSERT OR UPDATE OR DELETE ON public.users FOR EACH ROW EXECUTE PROCEDURE public.users_audit_trig();
Update the record for userid=101
app=# select * from public.users where userid=101; -[ RECORD 1 ]-+------------------------- userid | 101 username | resslement2s first_name | Rudiger last_name | Esslement city | Baltimore state | Maryland email | resslement2s@auda.org.au phone | 915-528-7033 is_like_sport | t app=# update public.users set first_name='Denish', last_name='Patel' where userid=101; UPDATE 1
Let’s query users_audit table..
app=# select * from audit.users_audit; -[ RECORD 1 ]---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- audit_ts | 2018-07-09 14:01:39.373666-04 operation | UPDATE username | denishpatel before | {"city": "Baltimore", "email": "resslement2s@auda.org.au", "phone": "915-528-7033", "state": "Maryland", "userid": 101, "username": "resslement2s", "last_name": "Esslement", "first_name": "Rudiger", "is_like_sport": true} after | {"city": "Baltimore", "email": "resslement2s@auda.org.au", "phone": "915-528-7033", "state": "Maryland", "userid": 101, "username": "resslement2s", "last_name": "Patel", "first_name": "Denish", "is_like_sport": true}
As you can see below the record is added to ONLY 2018_07 child table…
app=# select * from audit.users_audit_2018_08; (0 rows) app=# select * from audit.users_audit_2018_07; -[ RECORD 1 ]---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- audit_ts | 2018-07-09 14:01:39.373666-04 operation | UPDATE username | denishpatel before | {"city": "Baltimore", "email": "resslement2s@auda.org.au", "phone": "915-528-7033", "state": "Maryland", "userid": 101, "username": "resslement2s", "last_name": "Esslement", "first_name": "Rudiger", "is_like_sport": true} after | {"city": "Baltimore", "email": "resslement2s@auda.org.au", "phone": "915-528-7033", "state": "Maryland", "userid": 101, "username": "resslement2s", "last_name": "Patel", "first_name": "Denish", "is_like_sport": true}
Make sure constraint_exclusion=partition in postgresql.conf so query can use partition pruning. Below query grabs data from single partitioned table because WHERE clause includes partitioned_key (audit_ts).
app=# explain analyze select * from audit.users_audit where audit_ts >= '2018-07-01' and audit_ts < '2018-08-01' and after->>'userid'='101'; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------ Append (cost=4.17..9.52 rows=1 width=142) (actual time=0.010..0.011 rows=1 loops=1) -> Bitmap Heap Scan on users_audit_2018_07 (cost=4.17..9.52 rows=1 width=142) (actual time=0.010..0.010 rows=1 loops=1) Recheck Cond: ((audit_ts >= '2018-07-01 00:00:00-04'::timestamp with time zone) AND (audit_ts < '2018-08-01 00:00:00-04'::timestamp with time zone)) Filter: ((after ->> 'userid'::text) = '101'::text) Heap Blocks: exact=1 -> Bitmap Index Scan on users_audit_2018_07_audit_ts_operation_idx (cost=0.00..4.17 rows=2 width=0) (actual time=0.004..0.004 rows=1 loops=1) Index Cond: ((audit_ts >= '2018-07-01 00:00:00-04'::timestamp with time zone) AND (audit_ts < '2018-08-01 00:00:00-04'::timestamp with time zone)) Planning time: 0.239 ms Execution time: 0.032 ms (9 rows)
As you can see, it’s very easy to use partitioning in Postgres 10. In the next Postgres 11 release comes with even better features for partitioning.
Stay tuned!
Categories
Recent Blog
- Do you really need a DBA?
- Running VACUUM FULL in Non-blocking Mode
- Connection Scaling
- PSQL Helper: Managing Connections and Simplifying Queries
- Vacuum Those MVs!
- Working With Repmgr: Using Other 3rd Party Tools for Setting up a Standby
- Working with Amazon Aurora PostgreSQL: dag, standby rebooted again!
- Working with Amazon Aurora PostgreSQL: what happened to the stats?
- How to set application_name for psql command line utility?
- Is there a limit on number of partitions handled by Postgres?