Postgresql

Table of contents:

Slave

Rebuild slave (repmgr)

stop postgres
repmgr -h ${MASTER_SERVER} -c -F -U repmgr --without-barman standby clone
start postgres
repmgr standby unregister + register

Check replication status from DB

select client_addr, state, sent_lsn, write_lsn,
    flush_lsn, replay_lsn from pg_stat_replication;

Index

Check unused indexes

SELECT relname, indexrelname, idx_scan from pg_catalog.pg_stat_user_indexes where schemaname = 'public' order by idx_scan asc;

Compare sequential and index scans / summarize

SELECT
    relname,
    seq_scan,
    idx_scan,
    seq_tup_read,
    idx_tup_fetch,
    round(cast(idx_tup_fetch AS numeric) / (idx_tup_fetch + seq_tup_read) * 100, 2) AS hitratio_percentage,
    pg_size_pretty(pg_relation_size(to_regclass(relname))) as size_on_disk
FROM
    pg_stat_user_tables
WHERE
    (idx_tup_fetch + seq_tup_read)>0
        and
    cast(idx_tup_fetch AS numeric) / (idx_tup_fetch + seq_tup_read) < 0.8
        and
    pg_relation_size(to_regclass(relname))>800000
ORDER BY
    seq_tup_read desc;

Find cache utilization of tables

WITH hitratio as (
    SELECT
        relname,
        round(cast(heap_blks_hit as numeric) / (heap_blks_hit + heap_blks_read) * 100, 2) AS hit_pct,
        heap_blks_hit as "from cache",
        heap_blks_read as "from disk"
    FROM
        pg_statio_user_tables
    WHERE
        (heap_blks_hit + heap_blks_read)>0
)
    SELECT * FROM hitratio WHERE hit_pct < 80 ORDER BY hit_pct;

Show INSERT/UPDATE/DELETE ratio of tables

SELECT
    relname,
    round(cast(n_tup_ins AS numeric) / (n_tup_ins + n_tup_upd + n_tup_del) * 100, 2) AS ins_pct,
    round(cast(n_tup_upd AS numeric) / (n_tup_ins + n_tup_upd + n_tup_del) * 100, 2) AS upd_pct,
    round(cast(n_tup_del AS numeric) / (n_tup_ins + n_tup_upd + n_tup_del) * 100, 2) AS del_pct
FROM
    pg_stat_user_tables
WHERE
    (n_tup_ins + n_tup_upd + n_tup_del) > 0
ORDER BY relname;

Show all indexes with their definitions

SELECT indexname,indexdef FROM pg_indexes WHERE schemaname = 'public';

Rebuild index

Get index definition first (see above)

CREATE INDEX CONCURRENTLY index_name_NEW ${index_definition}
ALTER INDEX index_name RENAME TO index_name_OLD
ALTER INDEX index_name_NEW RENAME TO index_name
DROP INDEX index_name_OLD

List index size

    SELECT
    t.tablename,
    indexname,
    c.reltuples AS num_rows,
    pg_size_pretty(pg_relation_size(quote_ident(t.tablename)::text)) AS table_size,
    pg_size_pretty(pg_relation_size(quote_ident(indexrelname)::text)) AS index_size,
    CASE WHEN indisunique THEN 'Y'
       ELSE 'N'
    END AS UNIQUE,
    idx_scan AS number_of_scans,
    idx_tup_read AS tuples_read,
    idx_tup_fetch AS tuples_fetched
FROM pg_tables t
LEFT OUTER JOIN pg_class c ON t.tablename=c.relname
LEFT OUTER JOIN
    ( SELECT c.relname AS ctablename, ipg.relname AS indexname, x.indnatts AS number_of_columns, idx_scan, idx_tup_read, idx_tup_fetch, indexrelname, indisunique FROM pg_index x
           JOIN pg_class c ON c.oid = x.indrelid
           JOIN pg_class ipg ON ipg.oid = x.indexrelid
           JOIN pg_stat_all_indexes psai ON x.indexrelid = psai.indexrelid AND psai.schemaname = 'public' )
    AS foo
    ON t.tablename = foo.ctablename
WHERE t.schemaname='public'
ORDER BY 1,2;

Show duplicate indexes

SELECT pg_size_pretty(sum(pg_relation_size(idx))::bigint) as size,
       (array_agg(idx))[1] as idx1, (array_agg(idx))[2] as idx2,
       (array_agg(idx))[3] as idx3, (array_agg(idx))[4] as idx4
FROM (
    SELECT indexrelid::regclass as idx, (indrelid::text ||E'\n'|| indclass::text ||E'\n'|| indkey::text ||E'\n'||
                                         coalesce(indexprs::text,'')||E'\n' || coalesce(indpred::text,'')) as key
    FROM pg_index) sub
GROUP BY key HAVING count(*)>1
ORDER BY sum(pg_relation_size(idx)) DESC;

WAL

Force switch to a new WAL

select * from pg_switch_wal();

Check current WAL info

select * from pg_ls_waldir();

Bloat

Show all bloat

SELECT
  current_database(), schemaname, tablename, /*reltuples::bigint, relpages::bigint, otta,*/
  ROUND((CASE WHEN otta=0 THEN 0.0 ELSE sml.relpages::FLOAT/otta END)::NUMERIC,1) AS tbloat,
  CASE WHEN relpages < otta THEN 0 ELSE bs*(sml.relpages-otta)::BIGINT END AS wastedbytes,
  iname, /*ituples::bigint, ipages::bigint, iotta,*/
  ROUND((CASE WHEN iotta=0 OR ipages=0 THEN 0.0 ELSE ipages::FLOAT/iotta END)::NUMERIC,1) AS ibloat,
  CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta) END AS wastedibytes
FROM (
  SELECT
    schemaname, tablename, cc.reltuples, cc.relpages, bs,
    CEIL((cc.reltuples*((datahdr+ma-
      (CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::FLOAT)) AS otta,
    COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
    COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::FLOAT)),0) AS iotta -- very rough approximation, assumes all cols
  FROM (
    SELECT
      ma,bs,schemaname,tablename,
      (datawidth+(hdr+ma-(CASE WHEN hdr%ma=0 THEN ma ELSE hdr%ma END)))::NUMERIC AS datahdr,
      (maxfracsum*(nullhdr+ma-(CASE WHEN nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
    FROM (
      SELECT
        schemaname, tablename, hdr, ma, bs,
        SUM((1-null_frac)*avg_width) AS datawidth,
        MAX(null_frac) AS maxfracsum,
        hdr+(
          SELECT 1+COUNT(*)/8
          FROM pg_stats s2
          WHERE null_frac<>0 AND s2.schemaname = s.schemaname AND s2.tablename = s.tablename
        ) AS nullhdr
      FROM pg_stats s, (
        SELECT
          (SELECT current_setting('block_size')::NUMERIC) AS bs,
          CASE WHEN SUBSTRING(v,12,3) IN ('8.0','8.1','8.2') THEN 27 ELSE 23 END AS hdr,
          CASE WHEN v ~ 'mingw32' THEN 8 ELSE 4 END AS ma
        FROM (SELECT version() AS v) AS foo
      ) AS constants
      GROUP BY 1,2,3,4,5
    ) AS foo
  ) AS rs
  JOIN pg_class cc ON cc.relname = rs.tablename
  JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = rs.schemaname AND nn.nspname <> 'information_schema'
  LEFT JOIN pg_index i ON indrelid = cc.oid
  LEFT JOIN pg_class c2 ON c2.oid = i.indexrelid
) AS sml
ORDER BY wastedbytes DESC;

Ownership

Re-assign owners to some other user

REASSIGN OWNED BY old_role [, ...] TO new_role;

Change owner of selected tables

Just print what commands are needed:

select 'ALTER TABLE ' || t.tablename || ' OWNER TO new_user;'
 from  pg_tables t
where t.tablename like '%my_filter%'; # or any valid where case

Actually do the change (obviously dangerous if you don't set your filter restrictive):

DO
$do$
  DECLARE
    i pg_tables%rowtype;
  BEGIN
  FOR i IN SELECT * FROM pg_catalog.pg_tables where schemaname like 'public' and tablename like '%my_filter%'
  LOOP
    EXECUTE FORMAT(
      $$
        ALTER TABLE %I OWNER TO new_user;
      $$,
      i.tablename
    );
  END LOOP;
  END
$do$;

Misc

List all triggers

select * from information_schema.triggers;

Check current timeline id

select timeline_id from pg_control_checkpoint();

Repeat last command

\g

Watch last command

\watch {seconds}

Reset counters

select pg_stat_reset(); select pg_stat_statements_reset();

For archiver only:

SELECT pg_stat_reset_shared('archiver');

Check the size (as in disk space) of all databases

SELECT d.datname AS Name, pg_catalog.pg_get_userbyid(d.datdba) AS Owner,
  CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT')
    THEN pg_catalog.pg_size_pretty(pg_catalog.pg_database_size(d.datname))
    ELSE 'No Access'
  END AS SIZE
FROM pg_catalog.pg_database d
ORDER BY
  CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT')
    THEN pg_catalog.pg_database_size(d.datname)
    ELSE NULL
  END;

Check the size (as in disk space) of each table

SELECT nspname || '.' || relname AS "relation",
   pg_size_pretty(pg_total_relation_size(C.oid)) AS "total_size"
FROM pg_class C
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
   AND C.relkind <> 'i'
   AND nspname !~ '^pg_toast'
ORDER BY pg_total_relation_size(C.oid) DESC;

Show all foreign keys

SELECT
    tc.table_schema,
    tc.constraint_name,
    tc.table_name,
    kcu.column_name,
    ccu.table_schema AS foreign_table_schema,
    ccu.table_name AS foreign_table_name,
    ccu.column_name AS foreign_column_name
FROM
    information_schema.table_constraints AS tc
    JOIN information_schema.key_column_usage AS kcu
      ON tc.constraint_name = kcu.constraint_name
      AND tc.table_schema = kcu.table_schema
    JOIN information_schema.constraint_column_usage AS ccu
      ON ccu.constraint_name = tc.constraint_name
      AND ccu.table_schema = tc.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY';

# AND tc.table_name='mytable';

Export output of a query to CSV file

\COPY (SELECT * from users) To '/tmp/output.csv' With CSV;

Functions

Show definition of functions

\df+

Locks

See all locks

    SELECT t.relname, l.locktype, page, virtualtransaction, pid, mode, granted
    FROM pg_locks l, pg_stat_all_tables t
    WHERE l.relation = t.relid ORDER BY relation asc;

Detailed blocked/blocking list

SELECT blocked_locks.pid     AS blocked_pid,
    blocked_activity.usename  AS blocked_user,
    blocking_locks.pid     AS blocking_pid,
    blocking_activity.usename AS blocking_user,
    blocked_activity.query    AS blocked_statement,
    blocking_activity.query   AS current_statement_in_blocking_process
FROM pg_catalog.pg_locks         blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity  ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks         blocking_locks
    ON blocking_locks.locktype = blocked_locks.locktype
    AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
    AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
    AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
    AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
    AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
    AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
    AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
    AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
    AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
    AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.GRANTED;

Backup

Check if DB is in backup mode

select pg_is_in_backup();

Barman

Error: "please make sure WAL shipping is setup"

barman switch-wal --force --archive ${hostname}

Replication

Replication slots

Create:

select pg_create_physical_replication_slot('slotname');

Check:

select * from pg_replication_slots;

Remove:

pg_drop_replication_slot('slotname');

Connections

Show all active connections

SELECT
    pid
    ,datname
    ,usename
    ,application_name
    ,client_hostname
    ,client_port
    ,backend_start
    ,query_start
    ,query
    ,state
FROM pg_stat_activity
WHERE state = 'active';

JSON

Query a first level attribute

SELECT * FROM table WHERE column_name->>'1st_element' = 'value';

Query a 2nd level attribute

SELECT * FROM table WHERE column_name->'1st_element'->>'2nd_element' = "value";

Configuration

Show current vacuum settings

FROM pg_settings WHERE "name" ~* 'vac' ORDER BY "name";

Show per-table setting overrides

select relname, reloptions from pg_class;

Check if any change is waiting for restart

select name from pg_settings where pending_restart = 't';

List all variables which requires server restart to change

select name from pg_settings where context = 'postmaster';

Reload the config

select * from pg_reload_conf();