Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

PostgreSQL

The most advanced open source relational database in the world!

Concept

Architecture
    PostgreSQL cluster architecture and concept
Service
    Reliable service access via lb, proxy, pool
Database
    Define, create, and manage business databases
User Role
    Define, create, and manage users and roles
Auth / HBA
    Host-Based Authentication in Pigsty
Privileges
    Access Control with default roles and privileges

Administration

Kernel
    Replace vanilla PostgreSQL with exotic kernel forks
Extension
    Harness the synergistic power of PostgreSQL extensions
Configure
    Describe and configure PostgreSQL clusters
Parameter
    Customize postgres cluster with 120 parameters
Administration
    Run administrative tasks on PostgreSQL clusters
Playbook
    Control primitives with Ansible playbooks
Backup & PITR
    Backup and point-in-time recovery
Migration
    Zero-downtime blue-green deployment
Monitor
    Monitor existing PostgreSQL or RDS
Dashboard
    Visualized information with Grafana dashboards

1 - Architecture

PostgreSQL cluster architecture and concept

Entity-Relationships

There are four types of core entities in Pigsty’s PGSQL module:

  • Cluster: An autonomous PostgreSQL business unit, the top-level namespace for other entities.
  • Service: An abstraction of cluster ability, traffic routes, and expose services via different node ports.
  • Instance: A postgres server which consists of a group of running processes & files on a single node.
  • Node: An abstraction of hardware resources, which can be bare metal, virtual machine, or k8s pods.

Architecture

Here’s a PostgreSQL Cluster pg-test described in the config inventory:

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: replica }
    10.10.10.13: { pg_seq: 3, pg_role: replica }
  vars:
    pg_cluster: pg-test

It defines a HA PostgreSQL cluster looks like the above, and here are related entities in this cluster:

  • 1 PostgreSQL cluster: pg-test
  • 2 Instance Roles: primary & replica
  • 3 PostgreSQL Instances: pg-test-1, pg-test-2, pg-test-3
  • 3 Nodes: 10.10.10.11, 10.10.10.12, 10.10.10.13
  • 4 PostgreSQL Services, auto generated by default:

HA Description

The PostgreSQL cluster is managed by Patroni, which is a battle-tested HA solution for PostgreSQL. It will set up PG Replication on multiple nodes, and perform automatic failover when the primary node is down.

The backup is handled by pgBackRest, which is a powerful backup tool for PostgreSQL, which supports incremental backup/restore, compression, encryption, backup to local disk or S3 / MinIO.

The pgbouncer is a lightweight connection pooler which can increase the performance with high-concurrency. It is 1:1 deployed with the Postgres server and used by primary / replica services by default.

The services are exposed by HAProxy, which is a high-performance TCP/HTTP load balancer, it’s part of NODE module. And 4 default services are auto exposed in an idempotent way on all cluster nodes.

The application can visit any of the haproxy to access the Postgres cluster, and the traffic will be routed to the correct instance based on patroni health check endpoints. So failover is transparent to the apps.

The patroni requires a functioning ETCD in your deployment, and pgbackrest can use the optional MinIO as centralized backup storage; and monitoring exporters will collect metrics & logs into the Infra module.


Components

The PGSQL Node consists of the following components (some can be disabled)

Component Port Description
postgres 5432 PostgreSQL Server Process Managed by Patroni
pgbouncer 6432 Pgbouncer Connection Pool
pgbackrest - Backup and point-in-time-recovery tools
patroni 8008 Patroni HA Component, Manage postgres
primary @ haproxy 5433 Primary connection pool: Read/Write Service
replica @ haproxy 5434 Replica connection pool: Read-only Service
default @ haproxy 5436 Primary Direct Connect Service
offline @ haproxy 5438 Offline Direct Connect: Offline Read Service
pg_exporter 9630 postgres Monitoring Metrics Exporter
pgbouncer_exporter 9631 pgbouncer Monitoring Metrics Exporter
pgbackrest_exporter 9854 pgbackrest Monitoring Metrics Exporter
vip-manager - Bind VIP to the primary

Interaction

Meanwhile, the Infra Node consists of the following components which interact with PGSQL.

Component Port Domain Description
nginx 80 h.pigsty Web Service Portal (YUM/APT Repo)
alertmanager 9059 a.pigsty Alert Aggregation and delivery
prometheus 9058 p.pigsty Monitoring Time Series Database
grafana 3000 g.pigsty Visualization Platform
lok 3100 - Logging Collection Server
pushgateway 9091 - Collect One-Time Job Metrics
blackbox_exporter 9115 - Blackbox Probing
dnsmasq 53 - DNS Server
chronyd 123 - NTP Time Server
ansible - - Run playbooks
  • Cluster DNS is resolved by DNSMASQ on infra nodes
  • Cluster VIP is managed by vip-manager, which binds to cluster primary.
    • vip-manager will acquire cluster leader info written by patroni from etcd cluster directly
  • Cluster services are exposed by Haproxy on nodes, services are distinguished by node ports (543x).
    • Haproxy port 9101: monitoring metrics & stats & admin page
    • Haproxy port 5433: default service that routes to primary pgbouncer: primary
    • Haproxy port 5434: default service that routes to replica pgbouncer: replica
    • Haproxy port 5436: default service that routes to primary postgres: default
    • Haproxy port 5438: default service that routes to offline postgres: offline
    • HAProxy will route traffic based on health check information provided by patroni.
  • Pgbouncer is a connection pool that listens to port 6432
    • 1:1 Deployed with the Postgres server through a local unix socket.
    • Production traffic (Primary/Replica) will go through pgbouncer by default
    • Bypass pgbouncer for primary/replica services by setting pg_default_service_dest ) to postgres
    • Default/Offline service will always bypass pgbouncer and connect to target Postgres directly.
  • Postgres provides relational database services @ port 5432
    • Install PGSQL module on multiple nodes will automatically form a HA cluster based on replication.
    • PostgreSQL is supervised by patroni by default.
  • Patroni will supervise PostgreSQL server @ port 8008 by default
    • Patroni spawn postgres servers as the child process
    • Patroni uses etcd as DCS: config storage, failure detection, and leader election.
    • Patroni will provide Postgres information through a health check, used by HAProxy
    • Patroni metrics will be scraped by prometheus on infra nodes
  • PG Exporter will expose postgres metrics @ port 9630
  • Pgbouncer Exporter will expose pgbouncer metrics @ port 9631
    • Pgbouncer’s metrics will be scraped by prometheus on infra nodes
  • pgBackRest will work on the local repo by default (pgbackrest_method)
    • If local (default) is used as the backup repo, primary’s pg_fs_backup is used as local backup repo
    • If minio is used, pgBackRest will create the repo on the dedicated MinIO cluster
  • Postgres-related logs (postgres,pgbouncer,patroni,pgbackrest) are exposed by promtail @ port 9080
    • Promtail will send logs to Loki on infra node

Full ER Diagram

There is one config inventory file and one infra corresponding to a Pigsty deployment. And there may have multiple database clusters in a Pigsty deployment.

A Cluster / Instance may have multiple Databases, and Databases contain Tables and other Objects (Query, Index, Function, Seq, …).

2 - Configure

Describe and configure PostgreSQL clusters

You can define different types of instances & clusters.

  • Identity Parameters: Parameters used for describing a PostgreSQL cluster
  • Naming Convention: Parameters used for describing a PostgreSQL cluster
  • Primary: Define a single instance cluster.
  • Replica: Define a basic HA cluster with one primary & one replica.
  • Offline: Define a dedicated instance for OLAP/ETL/Interactive queries
  • Sync Standby: Enable synchronous commit to ensure no data loss.
  • Quorum Commit: Use quorum sync commit for an even higher consistency level.
  • Standby Cluster: Clone an existing cluster and follow it
  • Delayed Cluster: Clone an existing cluster for emergency data recovery
  • Citus Cluster: Define a Citus distributed database cluster

Identity Parameters

There are 4 REQUIRED parameters to describe a PostgreSQL Cluster:

Name Type Level Description
inventory_hostname ip Instance PG node IPv4 address
pg_cluster string Cluster PG database cluster name
pg_seq number Instance PG database instance id
pg_role enum Instance PG database instance role
  • pg_cluster: Name of the cluster, configured at the cluster level.
  • pg_role: Configured at the instance level, identifies the role of the instance.
    • the primary role will mark this instance as cluster leader (initially).
    • the replica is the default role, which marks this instance as common read-only replica.
    • the offline marks this instance as special read-only replica that serves the offline service.
  • pg_seq: Used to identify the instance within the cluster, a non-negative integer
    • Start from 0 or 1, incremental allocation in sequence, never change once assigned,
    • {{ pg_cluster }}-{{ pg_seq }} is used to uniquely identify the instance, i.e. pg_instance.
    • {{ pg_cluster }}-{{ pg_role }} is used to identify the services within the cluster, i.e. pg_service.
  • pg_shard and pg_group are used for horizontally sharding clusters, for citus & greenplum only.

These identities will be used in the entire system, for example, the metrics may look like:

pg_up{cls="pg-test", ins="pg-test-1", ip="10.10.10.11", job="pgsql"}
pg_up{cls="pg-test", ins="pg-test-2", ip="10.10.10.12", job="pgsql"}
pg_up{cls="pg-test", ins="pg-test-3", ip="10.10.10.13", job="pgsql"}

Sharding Clusters

You can use the OPTIONAL pg_shard and pg_group param to identify horizontal sharded clusters:

Name Type Level Description
pg_shard string C PG database shard name of cluster
pg_group number C PG database shard index of cluster

For example, Horizontal sharding with citus, greenplum or sharding it manually

pg-citus:
  hosts:
    10.10.10.10: { pg_group: 0, pg_cluster: pg-citus0 ,pg_seq: 1, pg_role: primary }
    10.10.10.11: { pg_group: 0, pg_cluster: pg-citus0 ,pg_seq: 2, pg_role: replica }
    10.10.10.12: { pg_group: 1, pg_cluster: pg-citus1 ,pg_seq: 1, pg_role: primary }
    10.10.10.13: { pg_group: 2, pg_cluster: pg-citus2 ,pg_seq: 1, pg_role: primary }
  vars:
    pg_mode: citus          # pgsql cluster mode: citus
    pg_shard: pg-citus      # citus shard name: pg-citus

Naming Convention

  • Cluster name should be a valid domain name matches [a-zA-Z0-9-]+, and ≤ 40 char
  • Service names are prefixed with cluster name, and suffixed with a single word join by -
  • Instance names are prefixed with cluster name and suffixed with an integer, join by -
  • Nodes are identified by its primary IPv4 address, hostname is used as secondary identifier
Entity Naming Examples
Cluster pg-meta, pg-test, …
Service pg-meta-primary, pg-test-replica, pg-test-offline, pg-test-standby, pg-meta-default
Instance pg-meta-1, pg-test-1, pg-test-2, pg-test-3,…
Node 10.10.10.10, 10.10.10.11, 10.10.10.12, 10.10.10.13

Version Policy

Pigsty follows the PostgreSQL Version Policy and “Officially” support the following major versions.

Major Minor Comment RPM EXT DEB EXT
18 18.1 The latest stable version (RECOMMENDED) 392 390
17 17.7 The sendary stable version (RECOMMENDED) 418 413
16 16.11 First release on 2023-09-14 420 412
15 15.15 First release on 2022-10-13 422 414
14 14.20 First release on 2021-09-30 410 402
13 13.23 First release on 2020-09-24, EOLed soon 382 371

Pigsty has PG 13 - 18 support. Lower major version (12-) “may” work, with no guarantee. For legacy PG version support, consider our professional services.

To use a different major version, configure the pg_version variable. Which can be globally configure with -v <ver> option. No further changed needed as long as they are available in local / upstream repo.

pg-v13:
  hosts: { 10.10.10.13: { pg_seq: 1 ,pg_role: primary } }
  vars:
    pg_cluster: pg-v13
    pg_version: 13

pg-v14:
  hosts: { 10.10.10.14: { pg_seq: 1 ,pg_role: primary } }
  vars:
    pg_cluster: pg-v14
    pg_version: 14

pg-v15:
  hosts: { 10.10.10.15: { pg_seq: 1 ,pg_role: primary } }
  vars:
    pg_cluster: pg-v15
    pg_version: 15

pg-v16:
  hosts: { 10.10.10.16: { pg_seq: 1 ,pg_role: primary } }
  vars:
    pg_cluster: pg-v16
    pg_version: 16

pg-v17:
  hosts: { 10.10.10.17: { pg_seq: 1 ,pg_role: primary } }
  vars:
    pg_cluster: pg-v17
    pg_version: 17

Primary

Let’s start with the simplest case, singleton meta:

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
  vars:
    pg_cluster: pg-test

Use the following command to create a primary database instance on the 10.10.10.11 node.

bin/pgsql-add pg-test

Replica

To add a physical replica, you can assign a new instance to pg-test with pg_role set to replica

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: replica }  # <--- newly added
  vars:
    pg_cluster: pg-test

You can create an entire cluster or append a replica to the existing cluster:

bin/pgsql-add pg-test               # init entire cluster in one-pass
bin/pgsql-add pg-test 10.10.10.12   # add replica to existing cluster

Offline

The offline instance is a dedicated replica to serve slow queries, ETL, OLAP traffic and interactive queries, etc…

To add an offline instance, assign a new instance with pg_role set to offline.

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: replica }
    10.10.10.13: { pg_seq: 3, pg_role: offline } # <--- newly added
  vars:
    pg_cluster: pg-test

Offline instance works like common replica instances, but it is used as a backup server in pg-test-replica service. That is to say, offline and primary instances serve only when all replica instances are down.

You can have ad hoc access control offline with pg_default_hba_rules and pg_hba_rules. It will apply to the offline instance and any instances with pg_offline_query flag.


Sync Standby

Pigsty uses asynchronous stream replication by default, which may have a small replication lag (10KB / 10ms). A small window of data loss may occur when the primary fails (can be controlled with pg_rpo), but it is acceptable for most scenarios.

But in some critical scenarios (e.g., financial transactions), data loss is totally unacceptable or read-your-write consistency is required. In this case, you can enable synchronous commit to ensure that.

To enable sync standby mode, you can simply use crit.yml template in pg_conf

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: replica }
    10.10.10.13: { pg_seq: 3, pg_role: replica }
  vars:
    pg_cluster: pg-test
    pg_conf: crit.yml   # <--- use crit template

To enable sync standby on existing clusters, config the cluster and enable synchronous_mode:

$ pg edit-config pg-test    # run on admin node with admin user
+++
-synchronous_mode: false    # <--- old value
+synchronous_mode: true     # <--- new value
 synchronous_mode_strict: false

Apply these changes? [y/N]: y

If synchronous_mode: true, the synchronous_standby_names parameter will be managed by patroni. It will choose a sync standby from all available replicas and write its name to the primary’s configuration file.


Quorum Commit

When sync standby is enabled, PostgreSQL will pick one replica as the standby instance, and all other replicas as candidates. Primary will wait until the standby instance flushes to disk before a commit is confirmed, and the standby instance will always have the latest data without any lags.

However, you can achieve an even higher/lower consistency level with the quorum commit (trade-off with availability).

For example, to have all 2 replicas to confirm a commit:

synchronous_mode: true          # make sure synchronous mode is enabled
synchronous_node_count: 2       # at least 2 nodes to confirm a commit

If you have more replicas and wish to have more sync standby, increase synchronous_node_count accordingly. Beware of adjust synchronous_node_count accordingly when you append or remove replicas.

The postgres synchronous_standby_names parameter will be managed by patroni:

synchronous_standby_names = '2 ("pg-test-3","pg-test-2")'

The classic quorum commit is to use majority of replicas to confirm a commit.

synchronous_mode: quorum        # use quorum commit
postgresql:
  parameters:                   # change the PostgreSQL parameter `synchronous_standby_names`, use the `ANY n ()` notion
    synchronous_standby_names: 'ANY 1 (*)'  # you can specify a list of standby names, or use `*` to match them all

Standby Cluster

You can clone an existing cluster and create a standby cluster, which can be used for migration, horizontal split, multi-az deployment, or disaster recovery.

A standby cluster’s definition is just the same as any other normal cluster, except there’s a pg_upstream defined on the primary instance.

For example, you have a pg-test cluster, to create a standby cluster pg-test2, the inventory may look like this:

# pg-test is the original cluster
pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
  vars: { pg_cluster: pg-test }

# pg-test2 is a standby cluster of pg-test.
pg-test2:
  hosts:
    10.10.10.12: { pg_seq: 1, pg_role: primary , pg_upstream: 10.10.10.11 } # <--- pg_upstream is defined here
    10.10.10.13: { pg_seq: 2, pg_role: replica }
  vars: { pg_cluster: pg-test2 }

And pg-test2-1, the primary of pg-test2 will be a replica of pg-test and serve as a Standby Leader in pg-test2.

Just make sure that the pg_upstream parameter is configured on the primary of the backup cluster to pull backups from the original upstream automatically.

bin/pgsql-add pg-test     # Creating the original cluster
bin/pgsql-add pg-test2    # Creating a Backup Cluster

Delayed Cluster

A delayed cluster is a special type of standby cluster, which is used to recover “drop-by-accident” ASAP.

For example, if you wish to have a cluster pg-testdelay which has the same data as 1-day ago pg-test cluster:

# pg-test is the original cluster
pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
  vars: { pg_cluster: pg-test }

# pg-testdelay is a delayed cluster of pg-test.
pg-testdelay:
  hosts:
    10.10.10.12: { pg_seq: 1, pg_role: primary , pg_upstream: 10.10.10.11, pg_delay: 1d }
    10.10.10.13: { pg_seq: 2, pg_role: replica }
  vars: { pg_cluster: pg-test2 }

You can also configure a replication delay on the existing standby cluster.

$ pg edit-config pg-testdelay
 standby_cluster:
   create_replica_methods:
   - basebackup
   host: 10.10.10.11
   port: 5432
+  recovery_min_apply_delay: 1h    # <--- add delay here

Apply these changes? [y/N]: y

When some tuples & tables are dropped by accident, you can advance this delayed cluster to a proper time point and select data from it.

It takes more resources, but can be much faster and have less impact than PITR


Citus Cluster

Pigsty has native citus support. Check the conf/citus.yml example.

To define a citus cluster, you have to specify the following parameters:

Besides, extra hba rules that allow ssl access from local & other data nodes are required. Which may looks like this

all:
  children:
    pg-citus0: # citus data node 0
      hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
      vars: { pg_cluster: pg-citus0 , pg_group: 0 }
    pg-citus1: # citus data node 1
      hosts: { 10.10.10.11: { pg_seq: 1, pg_role: primary } }
      vars: { pg_cluster: pg-citus1 , pg_group: 1 }
    pg-citus2: # citus data node 2
      hosts: { 10.10.10.12: { pg_seq: 1, pg_role: primary } }
      vars: { pg_cluster: pg-citus2 , pg_group: 2 }
    pg-citus3: # citus data node 3, with an extra replica
      hosts:
        10.10.10.13: { pg_seq: 1, pg_role: primary }
        10.10.10.14: { pg_seq: 2, pg_role: replica }
      vars: { pg_cluster: pg-citus3 , pg_group: 3 }
  vars:                               # global parameters for all citus clusters
    pg_mode: citus                    # pgsql cluster mode: citus
    pg_shard: pg-citus                # citus shard name: pg-citus
    patroni_citus_db: meta            # citus distributed database name
    pg_dbsu_password: DBUser.Postgres # all dbsu password access for citus cluster
    pg_users: [ { name: dbuser_meta ,password: DBUser.Meta ,pgbouncer: true ,roles: [ dbrole_admin ] } ]
    pg_databases: [ { name: meta ,extensions: [ { name: citus }, { name: postgis }, { name: timescaledb } ] } ]
    pg_hba_rules:
      - { user: 'all' ,db: all  ,addr: 127.0.0.1/32 ,auth: ssl ,title: 'all user ssl access from localhost' }
      - { user: 'all' ,db: all  ,addr: intra        ,auth: ssl ,title: 'all user ssl access from intranet'  }

And you can create distributed table & reference table on the coordinator node. Any data node can be used as the coordinator node since citus 11.2.

SELECT create_distributed_table('pgbench_accounts', 'aid'); SELECT truncate_local_data_after_distributing_table($$public.pgbench_accounts$$);
SELECT create_reference_table('pgbench_branches')         ; SELECT truncate_local_data_after_distributing_table($$public.pgbench_branches$$);
SELECT create_reference_table('pgbench_history')          ; SELECT truncate_local_data_after_distributing_table($$public.pgbench_history$$);
SELECT create_reference_table('pgbench_tellers')          ; SELECT truncate_local_data_after_distributing_table($$public.pgbench_tellers$$);

3 - Parameter

customize postgres cluster with 121 parameters

There are 121 parameters about the PGSQL module.

Section Count Description
PG_ID 11 Calculate & Check Postgres Identity - parameters for identifying PGSQL entities like instances and services
PG_BUSINESS 12 Postgres Business Object Definition - configuration for business users, databases, services, and authentication
PG_INSTALL 10 Install PGSQL Packages & Extensions - settings for database user setup, version selection, and package installation
PG_BOOTSTRAP 35 Init a HA Postgres Cluster with Patroni - comprehensive cluster initialization including data directories, networking, and high availability setup
PG_PROVISION 9 Create users, databases, and in-database objects - post-bootstrap provisioning of database objects and default configurations
PG_BACKUP 6 Setup backup repo with pgbackrest - backup and recovery configuration using pgbackrest
PG_ACCESS 16 Exposing pg service, bind vip and register DNS - service exposure, load balancing, VIP management, and DNS registration
PG_MONITOR 18 Add Monitor for PGSQL Instance - monitoring setup with various exporters for metrics collection
PG_REMOVE : Remove a Postgres Cluster
Name Type Level Comment
pg_safeguard bool G/C/A stop removal when enabled; false by default
pg_rm_data bool G/C/A remove postgres data during removal; true by default
pg_rm_backup bool G/C/A remove primary pgBackRest backup during removal; true by default
pg_rm_pkg bool G/C/A uninstall postgres packages during removal; true by default

PG_ID

Here are some common parameters used to identify PGSQL entities: instance, service, etc…


pg_mode

name: pg_mode, type: enum, level: C

pgsql cluster mode, pgsql by default, i.e. standard PostgreSQL cluster.

  • pgsql: Standard PostgreSQL cluster, default value.
  • citus: Horizontal sharding cluster with citus extension.
  • mssql: Babelfish MSSQL wire protocol compatible kernel.
  • ivory: IvorySQL Oracle compatible kernel.
  • polar: PolarDB for PostgreSQL kernel.
  • oracle: PolarDB for Oracle kernel.
  • gpsql: Greenplum / Cloudberry

If pg_mode is set to citus or gpsql, pg_shard and pg_group will be required for horizontal sharding clusters.


pg_cluster

name: pg_cluster, type: string, level: C

pgsql cluster name, REQUIRED identity parameter

The cluster name will be used as the namespace for PGSQL related resources within that cluster.

The naming needs to follow the specific naming pattern: [a-z][a-z0-9-]* to be compatible with the requirements of different constraints on the identity.


pg_seq

name: pg_seq, type: int, level: I

pgsql instance seq number, REQUIRED identity parameter

A serial number to identify these instances, unique within its cluster, starting from 0 or 1.


pg_role

name: pg_role, type: enum, level: I

pgsql role, REQUIRED, could be primary,replica,offline

Roles for PGSQL instance, can be: primary, replica, standby or offline.

  • primary: Primary, there is one and only one primary in a cluster.
  • replica: Replica for carrying online read-only traffic, there may be a slight replication delay through (10ms~100ms, 100KB).
  • standby: Special replica that is always synced with primary, there’s no replication delay & data loss on this replica. (currently same as replica)
  • offline: Offline replica for taking on offline read-only traffic, such as statistical analysis/ETL/personal queries, etc.

Identity params, required params, and instance-level params.


pg_instances

name: pg_instances, type: dict, level: I

define multiple pg instances on node in {port:ins_vars} format.

This parameter is reserved for multi-instance deployment on a single node which is not implemented in Pigsty yet.


pg_upstream

name: pg_upstream, type: ip, level: I

Upstream ip address for standby cluster or cascade replica

Setting pg_upstream is set on primary instance indicate that this cluster is a Standby Cluster, and will receiving changes from upstream instance, thus the primary is actually a standby leader.

Setting pg_upstream for a non-primary instance will explicitly set a replication upstream instance, if it is different from the primary IP Address, this instance will become a cascade replica. And it’s user’s responsibility to ensure that the upstream IP addr is another instance in the same cluster.


pg_shard

name: pg_shard, type: string, level: C

pgsql shard name, required identity parameter for sharding clusters (e.g. citus cluster), optional for common pgsql clusters.

When multiple pgsql clusters serve the same business together in a horizontally sharding style, Pigsty will mark this group of clusters as a Sharding Group.

pg_shard is the name of the shard group name. It’s usually the prefix of pg_cluster.

For example, if we have a sharding group pg-citus, and 4 clusters in it, there identity params will be:

cls pg_shard: pg-citus
cls pg_group = 0:   pg-citus0
cls pg_group = 1:   pg-citus1
cls pg_group = 2:   pg-citus2
cls pg_group = 3:   pg-citus3

pg_group

name: pg_group, type: int, level: C

pgsql shard index number, required identity for sharding clusters, optional for common pgsql clusters.

Sharding cluster index of a sharding group, used in pairs with pg_shard. You can use any non-negative integer as the index number.


gp_role

name: gp_role, type: enum, level: C

greenplum/matrixdb role of this cluster, could be master or segment

  • master: mark the postgres cluster as greenplum master, which is the default value
  • segment mark the postgres cluster as greenplum segment

This parameter is only used for greenplum & derived databases, and is ignored for common pgsql cluster.


pg_exporters

name: pg_exporters, type: dict, level: C

additional pg_exporters to monitor remote postgres instances, default values: {}

If you wish to monitor remote postgres instances, define them in pg_exporters and load them with pgsql-monitor.yml playbook.

pg_exporters: # list all remote instances here, alloc a unique unused local port as k
    20001: { pg_cluster: pg-foo, pg_seq: 1, pg_host: 10.10.10.10 }
    20004: { pg_cluster: pg-foo, pg_seq: 2, pg_host: 10.10.10.11 }
    20002: { pg_cluster: pg-bar, pg_seq: 1, pg_host: 10.10.10.12 }
    20003: { pg_cluster: pg-bar, pg_seq: 1, pg_host: 10.10.10.13 }

Check PGSQL Monitoring for details.


pg_offline_query

name: pg_offline_query, type: bool, level: I

set to true to enable offline queries on this instance

default value is false

When this parameter is enabled for a PostgreSQL instance, users belonging to the dbrole_offline group can directly connect to that PostgreSQL instance to perform offline queries (slow queries, interactive queries, ETL/analytical queries).

Instances with this flag are functionally similar to setting pg_role = offline, with the only difference being that offline instances by default do not handle replica service requests, as they exist specifically as dedicated offline/analytical replica instances.

If you don’t have spare instances that can be dedicated to this purpose, you can select a regular replica and enable this parameter at the instance level to accommodate offline queries when needed.


PG_BUSINESS

Database credentials, In-Database Objects that need to be taken care of by Users.

Default Database Users:

WARNING: YOU HAVE TO CHANGE THESE DEFAULT PASSWORDs in production environment.

# postgres business object definition, overwrite in group vars
pg_users: []                      # postgres business users
pg_databases: []                  # postgres business databases
pg_services: []                   # postgres business services
pg_hba_rules: []                  # business hba rules for postgres
pgb_hba_rules: []                 # business hba rules for pgbouncer
# global credentials, overwrite in global vars
pg_dbsu_password: ''              # dbsu password, empty string means no dbsu password by default
pg_replication_username: replicator
pg_replication_password: DBUser.Replicator
pg_admin_username: dbuser_dba
pg_admin_password: DBUser.DBA
pg_monitor_username: dbuser_monitor
pg_monitor_password: DBUser.Monitor

pg_users

name: pg_users, type: user[], level: C

postgres business users, defined at cluster level.

default values: [], each object in the array defines a User/Role. Examples:

- name: dbuser_meta               # REQUIRED, `name` is the only mandatory field of a user definition
  password: DBUser.Meta           # optional, the password, can be a scram-sha-256 hash string or plain text
  login: true                     # optional, can log in, true by default (new biz ROLE should be false)
  superuser: false                # optional, is superuser? false by default
  createdb: false                 # optional, can create database? false by default
  createrole: false               # optional, can create role? false by default
  inherit: true                   # optional, can this role use inherited privileges? true by default
  replication: false              # optional, can this role do replication? false by default
  bypassrls: false                # optional, can this role bypass row level security? false by default
  pgbouncer: true                 # optional, add this user to pgbouncer userlist? false by default (production user should be true explicitly)
  connlimit: -1                   # optional, user connection limit, default -1 disable limit
  expire_in: 3650                 # optional, now + n days when this role is expired (OVERWRITE expire_at)
  expire_at: '2030-12-31'         # optional, YYYY-MM-DD 'timestamp' when this role is expired  (OVERWRITTEN by expire_in)
  comment: pigsty admin user      # optional, comment string for this user/role
  roles: [dbrole_admin]           # optional, belonged roles. default roles are: dbrole_{admin,readonly,readwrite,offline}
  parameters: {}                  # optional, role level parameters with `ALTER ROLE SET`
  pool_mode: transaction          # optional, pgbouncer pool mode at user level, transaction by default
  pool_connlimit: -1              # optional, max database connections at user level, default -1 disable limit
  search_path: public             # key value config parameters according to postgresql documentation (e.g: use pigsty as default search_path)

The only mandatory field of a user definition is name, and the rest are optional.


pg_databases

name: pg_databases, type: database[], level: C

postgres business databases, defined at cluster level.

default values: [], each object in the array defines a Database. Examples:

- name: meta                      # REQUIRED, `name` is the only mandatory field of a database definition
  baseline: cmdb.sql              # optional, database sql baseline path, (relative path among ansible search path, e.g files/)
  pgbouncer: true                 # optional, add this database to pgbouncer database list? true by default
  schemas: [pigsty]               # optional, additional schemas to be created, array of schema names
  extensions:                     # optional, additional extensions to be installed: array of `{name[,schema]}`
    - { name: postgis , schema: public }  # You can specify which schema to install the extension in, or leave it unspecified (if unspecified, it will be installed in the first schema of search_path)
    - { name: timescaledb }               # For example, some extensions will create and use fixed schemas, so you don't need to specify a schema.
    - vector                              # You can also directly use a string to specify the extension name
  comment: pigsty meta database   # optional, comment string for this database
  owner: postgres                 # optional, database owner, postgres by default
  template: template1             # optional, which template to use, template1 by default
  encoding: UTF8                  # optional, database encoding, UTF8 by default. (MUST same as template database)
  locale: C                       # optional, database locale, C by default.  (MUST same as template database)
  lc_collate: C                   # optional, database collate, C by default. (MUST same as template database)
  lc_ctype: C                     # optional, database ctype, C by default.   (MUST same as template database)
  tablespace: pg_default          # optional, default tablespace, 'pg_default' by default.
  allowconn: true                 # optional, allow connection, true by default. false will disable connect at all
  revokeconn: false               # optional, revoke public connection privilege. false by default. (leave connect with grant option to owner)
  register_datasource: true       # optional, register this database to grafana datasources? true by default
  connlimit: -1                   # optional, database connection limit, default -1 disable limit
  pool_auth_user: dbuser_meta     # optional, all connection to this pgbouncer database will be authenticated by this user
  pool_mode: transaction          # optional, pgbouncer pool mode at database level, default transaction
  pool_size: 64                   # optional, pgbouncer pool size at database level, default 64
  pool_size_reserve: 32           # optional, pgbouncer pool size reserve at database level, default 32
  pool_size_min: 0                # optional, pgbouncer pool size min at database level, default 0
  pool_max_db_conn: 100           # optional, max database connections at database level, default 100

In each database definition, the DB name is mandatory and the rest are optional.


pg_services

name: pg_services, type: service[], level: C

postgres business services exposed via haproxy, has to be defined at cluster level.

You can define ad hoc services with pg_services in additional to default pg_default_services

default values: [], each object in the array defines a Service. Examples:

- name: standby                   # required, service name, the actual svc name will be prefixed with `pg_cluster`, e.g: pg-meta-standby
  port: 5435                      # required, service exposed port (work as kubernetes service node port mode)
  ip: "*"                         # optional, service bind ip address, `*` for all ip by default
  selector: "[]"                  # required, service member selector, use JMESPath to filter inventory
  dest: default                   # optional, destination port, default|postgres|pgbouncer|<port_number>, 'default' by default
  check: /sync                    # optional, health check url path, / by default
  backup: "[? pg_role == `primary`]"  # backup server selector
  maxconn: 3000                   # optional, max allowed front-end connection
  balance: roundrobin             # optional, haproxy load balance algorithm (roundrobin by default, other: leastconn)
  options: 'inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100'

pg_hba_rules

name: pg_hba_rules, type: hba[], level: C

business hba rules for postgres

default values: [], each object in array is an HBA Rule definition:

Which are array of hba object, each hba object may look like

# RAW HBA RULES
- title: allow intranet password access
  role: common
  rules:
    - host   all  all  10.0.0.0/8      md5
    - host   all  all  172.16.0.0/12   md5
    - host   all  all  192.168.0.0/16  md5
  • title: Rule Title, transform into comment in the hba file
  • rules: Array of strings, each string is a raw hba rule record
  • role : Applied roles, where to install these hba rules
    • common: apply for all instances
    • primary, replica,standby, offline: apply on corresponding instances with that pg_role.
    • special case: HBA rule with role == 'offline' will be installed on instance with pg_offline_query flag

or you can use another alias form

- addr: 'intra'    # world|intra|infra|admin|local|localhost|cluster|<cidr>
  auth: 'pwd'      # trust|pwd|ssl|cert|deny|<official auth method>
  user: 'all'      # all|${dbsu}|${repl}|${admin}|${monitor}|<user>|<group>
  db: 'all'        # all|replication|....
  rules: []        # raw hba string precedence over above all
  title: allow intranet password access

pg_default_hba_rules is similar to this, but is used for global HBA rule settings


pgb_hba_rules

name: pgb_hba_rules, type: hba[], level: C

business hba rules for pgbouncer, default values: []

Similar to pg_hba_rules, array of hba rule object, except this is for pgbouncer.


pg_replication_username

name: pg_replication_username, type: username, level: G

postgres replication username, replicator by default

This parameter is globally used, it is not wise to change it.


pg_replication_password

name: pg_replication_password, type: password, level: G

postgres replication password, DBUser.Replicator by default

WARNING: CHANGE THIS IN PRODUCTION ENVIRONMENT!!!!


pg_admin_username

name: pg_admin_username, type: username, level: G

postgres admin username, dbuser_dba by default, which is a global postgres superuser.

default values: dbuser_dba


pg_admin_password

name: pg_admin_password, type: password, level: G

postgres admin password in plain text, DBUser.DBA by default

WARNING: CHANGE THIS IN PRODUCTION ENVIRONMENT!!!!


pg_monitor_username

name: pg_monitor_username, type: username, level: G

postgres monitor username, dbuser_monitor by default, which is a global monitoring user.


pg_monitor_password

name: pg_monitor_password, type: password, level: G

postgres monitor password, DBUser.Monitor by default.

Try not using the @:/ character in the password to avoid problems with PGURL string.

WARNING: CHANGE THIS IN PRODUCTION ENVIRONMENT!!!!


pg_dbsu_password

name: pg_dbsu_password, type: password, level: G/C

PostgreSQL dbsu password for pg_dbsu, empty string means no dbsu password, which is the default behavior.

Set this password will allow a well-know dbsu login from remote!

It’s not recommended to set the well-known dbsu (postgres) password for common PGSQL clusters, except for a good reason, such as using pg_mode = citus.


PG_INSTALL

This section is responsible for installing PostgreSQL & Extensions.

If you wish to install a different major version, make sure repo packages exists and overwrite pg_version on cluster level.

To install extra extensions, overwrite pg_extensions on cluster level. Beware that not all extensions are available with other major versions.

pg_dbsu: postgres                 # os dbsu name, postgres by default, better not change it
pg_dbsu_uid: 26                   # os dbsu uid and gid, 26 for default postgres users and groups
pg_dbsu_sudo: limit               # dbsu sudo privilege, none,limit,all,nopass. limit by default
pg_dbsu_home: /var/lib/pgsql      # postgresql home directory, `/var/lib/pgsql` by default
pg_dbsu_ssh_exchange: true        # exchange postgres dbsu ssh key among same pgsql cluster
pg_version: 18                    # postgres major version to be installed, 18 by default
pg_bin_dir: /usr/pgsql/bin        # postgres binary dir, `/usr/pgsql/bin` by default
pg_log_dir: /pg/log/postgres      # postgres log dir, `/pg/log/postgres` by default
pg_packages:                      # pg packages to be installed, alias can be used
  - pgsql-main pgsql-common
pg_extensions: []                 # pg extensions to be installed, alias can be used

pg_dbsu

name: pg_dbsu, type: username, level: C

OS DBSU name, postgres by default, it’s not wise to change it.

When installing Greenplum / MatrixDB, set this parameter to the corresponding default value: gpadmin|mxadmin.


pg_dbsu_uid

name: pg_dbsu_uid, type: int, level: C

OS DBSU uid and gid, 26 for default postgres users and groups, which is consistent with the official pgdg RPM.

For Ubuntu/Debian, there’s no default postgres UID/GID, consider using another ad hoc value, such as 543 instead.


pg_dbsu_sudo

name: pg_dbsu_sudo, type: enum, level: C

OS DBSU sudo privilege, could be none, limit ,all ,nopass. limit by default

  • none: No Sudo privilege
  • limit: Limited sudo privilege to execute systemctl commands for database-related components, default.
  • all: Full sudo privilege, password required.
  • nopass: Full sudo privileges without a password (not recommended).

default values: limit, which only allow sudo systemctl <start|stop|reload> <postgres|patroni|pgbouncer|...>

Available sudo services:

  • patroni
  • pgbouncer
  • postgres
  • pg_exporter
  • pgbackrest
  • pgbouncer_exporter
  • pgbackrest_exporter
  • vip-manager
  • haproxy (reload only)

pg_dbsu_home

name: pg_dbsu_home, type: path, level: C

postgresql home directory, /var/lib/pgsql by default, which is consistent with the official pgdg RPM.


pg_dbsu_ssh_exchange

name: pg_dbsu_ssh_exchange, type: bool, level: C

exchange postgres os dbsu ssh key among pgsql instances?

default value is true, means the dbsu can ssh to each other among the playbook execution hosts.

For scenarios where ssh access is strictly limited, you can set it to false.

Please note that SSH key exchange occurs between instances that are executing the same playbook. If you run the pgsql role for a single PostgreSQL cluster, the key exchange will occur between all instances in that cluster. If you run the pgsql role for all PostgreSQL clusters, the key exchange will occur between all instances, which can lead to severe combinatorial explosions for large clusters. If any instance involved in the key exchange does not have the pg_dbsu user, the key exchange will fail for that instance, but will not affect other instances.


pg_version

name: pg_version, type: enum, level: C

postgres major version to be installed, 18 by default

Note that PostgreSQL physical stream replication cannot cross major versions, so do not configure this on instance level.

You can use the parameters in pg_packages and pg_extensions to install rpm/deb for the specific pg major version.


pg_bin_dir

name: pg_bin_dir, type: path, level: C

postgres binary dir, /usr/pgsql/bin by default

The default value is a soft link created manually during the installation process, pointing to the specific Postgres version dir installed.

For example /usr/pgsql -> /usr/pgsql-17. For more details, check PGSQL File Structure for details.


pg_log_dir

name: pg_log_dir, type: path, level: C

postgres log dir, /pg/log/postgres by default.

caveat: if pg_log_dir is prefixed with pg_data it will not be created explicitly (it will be created by postgres itself then).


pg_packages

name: pg_packages, type: string[], level: C

PostgreSQL packages (rpm/deb) to be installed. This is an array of package names, where each element is a comma or space-separated list of PG package names or aliases.

Default value: [ pgsql-main pgsql-common ]

These default values are two aliases that are translated through alias mapping into the main RPM/DEB package names for the current PG major version, as well as version-independent common components (such as Patroni, PgBackrest, etc.)

Since Pigsty v3, you can use the alias lists specified in the system configuration in roles/node_id/vars for this parameter.

The advantage of using package aliases is that you don’t need to worry about package names, architectures, and major version numbers for PostgreSQL-related packages across different system platforms, thus abstracting away differences between operating systems:

Packages defined here will first be translated through the package_map, then undergo PG major version number substitution, and finally install the actual RPM/DEB packages.

You can also directly specify the final RPM/DEB package names to be installed, where version placeholders like ${pg_version} or $v in the package name will be replaced with the specific major version number pg_version.


pg_extensions

name: pg_extensions, type: string[], level: C

PG extensions to be installed (rpm/deb), this is an array of software package names, each element is a comma or space separated PG extension package name.

This parameter is similar to pg_packages, but is usually used to specify the extension to be installed @ global | cluster level, and the software packages specified here will be upgraded to the latest available version.

The default value of this parameter is the three most important extension plugins in the PG extension ecosystem: postgis, timescaledb, pgvector.

pg_extensions: []

The complete list of extensions can be found in auto generated config

The full extension list can be found in roles/node_id/vars and listed in Extension List.


PG_BOOTSTRAP

Bootstrap postgres cluster with patroni.

It also init cluster template databases with default roles, schemas & extensions & default privileges specified in PG_PROVISION

pg_data: /pg/data                 # postgres data directory, `/pg/data` by default
pg_fs_main: /data/postgres        # postgres main data directory, `/data/postgres` by default
pg_fs_backup: /data/backups       # postgres backup data directory, `/data/backups` by default
pg_storage_type: SSD              # storage type for pg main data, SSD,HDD, SSD by default
pg_dummy_filesize: 64MiB          # size of `/pg/dummy`, hold 64MB disk space for emergency use
pg_listen: '0.0.0.0'              # postgres/pgbouncer listen addresses, comma separated list
pg_port: 5432                     # postgres listen port, 5432 by default
pg_localhost: /var/run/postgresql # postgres unix socket dir for localhost connection
patroni_enabled: true             # if disabled, no postgres cluster will be created during init
patroni_mode: default             # patroni working mode: default,pause,remove
pg_namespace: /pg                 # top level key namespace in etcd, used by patroni & vip
patroni_port: 8008                # patroni listen port, 8008 by default
patroni_log_dir: /pg/log/patroni  # patroni log dir, `/pg/log/patroni` by default
patroni_ssl_enabled: false        # secure patroni RestAPI communications with SSL?
patroni_watchdog_mode: off        # patroni watchdog mode: automatic, required, off. off by default
patroni_username: postgres        # patroni restapi username, `postgres` by default
patroni_password: Patroni.API     # patroni restapi password, `Patroni.API` by default
pg_primary_db: postgres           # primary database name, used by citus,etc... postgres by default
pg_parameters: {}                 # extra parameters in postgresql.auto.conf
pg_files: []                      # extra files to be copied to postgres data directory (e.g. license)
pg_conf: oltp.yml                 # config template: oltp,olap,crit,tiny. `oltp.yml` by default
pg_max_conn: auto                 # postgres max connections, `auto` will use recommended value
pg_shared_buffer_ratio: 0.25      # postgres shared buffers ratio, 0.25 by default, 0.1~0.4
pg_rto: 30                        # recovery time objective in seconds, `30s` by default
pg_rpo: 1048576                   # recovery point objective in bytes, `1MiB` at most by default
pg_libs: 'pg_stat_statements, auto_explain'  # preloaded libraries, `pg_stat_statements,auto_explain` by default
pg_delay: 0                       # replications apply delay for standby cluster leader
pg_checksum: true                 # enable data checksum for postgres cluster?
pg_pwd_enc: scram-sha-256         # passwords encryption algorithm: md5,scram-sha-256
pg_encoding: UTF8                 # database cluster encoding, `UTF8` by default
pg_locale: C                      # database cluster local, `C` by default
pg_lc_collate: C                  # database cluster collate, `C` by default
pg_lc_ctype: C                    # database character type, `C` by default
#pgsodium_key: ""                 # pgsodium key, 64 hex digits, default to sha256(pg_cluster)
#pgsodium_getkey_script: ""       # pgsodium getkey script path, pgsodium_getkey by default

pg_data

name: pg_data, type: path, level: C

postgres data directory, /pg/data by default

default values: /pg/data, DO NOT CHANGE IT.

It’s a soft link that points to the underlying data directory.


pg_fs_main

name: pg_fs_main, type: path, level: C

postgres main data directory, /data/postgres by default.

This directory will be created and owned by the pg_dbsu user, and it will be used as the main data directory for postgres.

If your main data directory node_data is changed, consider changing this parameter as well.

It’s recommended to use NVME SSD for postgres main data storage, Pigsty is optimized for SSD storage by default. If you are using HDD storage, consider changing the pg_storage_type to HDD to optimize for HDD storage.


pg_fs_backup

name: pg_fs_backup, type: path, level: C

postgres backup data directory, /data/backups by default

This directory will be created and owned by the pg_dbsu user, and it will be used as the local backup storage for postgres.

Local backup is enabled by default on primary pg cluster. If you are using the default pgbackrest_method = local, it is recommended to have a separate disk for backup storage. The backup disk should be large enough to hold all your backups, at least enough for 3 base backups + 2-day WAL archive. This is usually not a problem since you can use affordable and large HDD for that.

It’s optional if you are using remote / centralized backup storage (e.g. pgbackrest_method = minio).


pg_storage_type

name: pg_storage_type, type: enum, level: C

storage type for pg main data, SSD,HDD, SSD by default

default values: SSD, it will affect some tuning parameters, such as random_page_cost & effective_io_concurrency


pg_dummy_filesize

name: pg_dummy_filesize, type: size, level: C

size of /pg/dummy, default values: 64MiB, which hold 64MB disk space for emergency use

When the disk is full, removing the placeholder file can free up some space for emergency use, it is recommended to set at least 8GiB for production use.


pg_listen

name: pg_listen, type: ip, level: C

postgres/pgbouncer listen address, 0.0.0.0 (all ipv4 addr) by default

You can use placeholder in this variable:

  • ${ip}: translate to inventory_hostname, which is primary private IP address in the inventory
  • ${vip}: if pg_vip_enabled, this will translate to host part of pg_vip_address
  • ${lo}: will translate to 127.0.0.1

For example: '${ip},${lo}' or '${ip},${vip},${lo}'.


pg_port

name: pg_port, type: port, level: C

postgres listen port, 5432 by default.


pg_localhost

name: pg_localhost, type: path, level: C

postgres unix socket dir for localhost connection, default values: /var/run/postgresql

The Unix socket dir for PostgreSQL and Pgbouncer local connection, which is used by pg_exporter and patroni.


pg_namespace

name: pg_namespace, type: path, level: C

top level key namespace in etcd, used by patroni & vip, default values is: /pg , and it’s not recommended to change it.


patroni_enabled

name: patroni_enabled, type: bool, level: C

if disabled, no postgres cluster will be created during init

default value is true, If disabled, Pigsty will skip pulling up patroni (thus postgres).

This option is useful when trying to add some components to an existing postgres instance.


patroni_mode

name: patroni_mode, type: enum, level: C

patroni working mode: default, pause, remove

default values: default

  • default: Bootstrap PostgreSQL cluster with Patroni
  • pause: Just like default, but entering maintenance mode after bootstrap
  • remove: Init the cluster with Patroni, them remove Patroni and use raw PostgreSQL instead.

patroni_port

name: patroni_port, type: port, level: C

patroni listening port, 8008 by default, changing it is not recommended.

The Patroni API server listens to this port for health checking & API requests.


patroni_log_dir

name: patroni_log_dir, type: path, level: C

patroni log dir, /pg/log/patroni by default, which will be collected by promtail.


patroni_ssl_enabled

name: patroni_ssl_enabled, type: bool, level: G

Secure patroni RestAPI communications with SSL? default value is false

This parameter is a global flag that can only be set before deployment.

Since if SSL is enabled for patroni, you’ll have to perform healthcheck, metrics scrape, and API call with HTTPS instead of HTTP.


patroni_watchdog_mode

name: patroni_watchdog_mode, type: string, level: C

In case of primary failure, patroni can use watchdog to fencing the old primary node to avoid split-brain.

patroni watchdog mode: automatic, required, off:

  • off: not using watchdog. avoid fencing at all. This is the default value.
  • automatic: Enable watchdog if the kernel has softdog module enabled and watchdog is owned by dbsu
  • required: Force watchdog, refuse to start if softdog is not available

default value is off, you should not enable watchdog on infra nodes to avoid fencing.

For those critical systems where data consistency prevails over availability, it is recommended to enable watchdog.

Beware that if all your traffic is accessed via haproxy, there is no risk of brain split at all.


patroni_username

name: patroni_username, type: username, level: C

patroni restapi username, postgres by default, used in pair with patroni_password

Patroni unsafe RESTAPI is protected by username/password by default, check Config Cluster and Patroni RESTAPI for details.


patroni_password

name: patroni_password, type: password, level: C

patroni restapi password, Patroni.API by default

WARNING: CHANGE THIS IN PRODUCTION ENVIRONMENT!!!!


pg_primary_db

name: pg_primary_db, type: string, level: C

primary database name, used by citus,etc… , postgres by default

Patroni 3.0’s native citus will specify a managed database for citus. which is created by patroni itself.


pg_parameters

Parameter Name: pg_parameters, Type: dict, Level: G/C/I

This parameter is used to specify and manage configuration parameters in postgresql.auto.conf.

After all instances in the cluster have completed initialization, the pg_param task will sequentially overwrite the key/value pairs in this dictionary to /pg/data/postgresql.auto.conf.

Note: Please do not manually modify this configuration file, or use ALTER SYSTEM to change cluster configuration parameters. Any changes will be overwritten during the next configuration sync.

This variable has a higher priority than the cluster configuration in Patroni/DCS (i.e., it has a higher priority than the cluster configuration edited by Patroni edit-config). Therefore, it can typically override the cluster default parameters at the instance level.

When your cluster members have different specifications (not recommended!), you can fine-tune the configuration of each instance using this parameter.

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary , pg_parameters: { shared_buffers: '5GB' } }
    10.10.10.12: { pg_seq: 2, pg_role: replica , pg_parameters: { shared_buffers: '4GB' } }
    10.10.10.13: { pg_seq: 3, pg_role: replica , pg_parameters: { shared_buffers: '3GB' } }

Please note that some important cluster parameters (which have requirements for primary and replica parameter values) are managed directly by Patroni through command-line parameters and have the highest priority. These cannot be overridden by this method. For these parameters, you must use Patroni edit-config for management and configuration.

PostgreSQL parameters that must remain consistent across primary and replicas (inconsistency will prevent the replica from starting!):

  • wal_level
  • max_connections
  • max_locks_per_transaction
  • max_worker_processes
  • max_prepared_transactions
  • track_commit_timestamp

Parameters that should ideally remain consistent across primary and replicas (considering the possibility of primary-replica switch):

  • listen_addresses
  • port
  • cluster_name
  • hot_standby
  • wal_log_hints
  • max_wal_senders
  • max_replication_slots
  • wal_keep_segments
  • wal_keep_size

You can set non-existent parameters (such as GUCs from extensions), but changing existing configurations to illegal values may prevent PostgreSQL from starting. Please configure with caution!


pg_files

Parameter Name: pg_files, Type: path[], Level: C

Designates a list of files to be copied to the {{ pg_data }} directory. The default value is an empty array: [].

Files specified in this parameter will be copied to the {{ pg_data }} directory. This is mainly used to distribute license files required by special commercial versions of the PostgreSQL kernel.

Currently, only the PolarDB (Oracle-compatible) kernel requires a license file. For example, you can place the license.lic file in the files/ directory and specify it in pg_files:

pg_files: [ license.lic ]

pg_conf

name: pg_conf, type: enum, level: C

config template: {oltp,olap,crit,tiny}.yml, oltp.yml by default

  • tiny.yml: optimize for tiny nodes, virtual machines, small demo, (18Core, 116GB)
  • oltp.yml: optimize for OLTP workloads and latency-sensitive applications, (4C8GB+), which is the default template
  • olap.yml: optimize for OLAP workloads and throughput (4C8G+)
  • crit.yml: optimize for data consistency and critical applications (4C8G+)

default values: oltp.yml, but configure procedure will set this value to tiny.yml if current node is a tiny node.

You can have your own template, just put it under templates/<mode>.yml and set this value to the template name.


pg_max_conn

name: pg_max_conn, type: int, level: C

postgres max connections, You can specify a value between 50 and 5000, or use auto to use recommended value.

default value is auto, which will set max connections according to the pg_conf and pg_default_service_dest.

  • tiny: 250
  • olap: 500
  • crit: 500 (pgbouncer) / 1000 (postgres)
  • oltp: 500 (pgbouncer) / 1000 (postgres)

It’s not recommended to set this value greater than 5000, otherwise you have to increase the haproxy service connection limit manually as well.

Pgbouncer’s transaction pooling can alleviate the problem of too many OLTP connections, but it’s not recommended to use it in OLAP scenarios.


pg_shared_buffer_ratio

name: pg_shared_buffer_ratio, type: float, level: C

postgres shared buffer memory ratio, 0.25 by default, 0.1~0.4

default values: 0.25, means 25% of node memory will be used as PostgreSQL shard buffers.

Setting this value greater than 0.4 (40%) is usually not a good idea.

Note that shared buffer is only part of shared memory in PostgreSQL, to calculate the total shared memory, use show shared_memory_size_in_huge_pages;.


pg_rto

name: pg_rto, type: int, level: C

recovery time objective in seconds, This will be used as Patroni TTL value, 30s by default.

If a primary instance is missing for such a long time, a new leader election will be triggered.

Decreasing the value can reduce the unavailable time (unable to write) of the cluster during failover, but it will make the cluster more sensitive to network jitter, thus increase the chance of false-positive failover.

Config this according to your network condition and expectation to trade-off between chance and impact, the default value is 30s, and it will be populated to the following patroni parameters:

# the TTL to acquire the leader lock (in seconds). Think of it as the length of time before initiation of the automatic failover process. Default value: 30
ttl: {{ pg_rto }}

# the number of seconds the loop will sleep. Default value: 10 , this is patroni check loop interval
loop_wait: {{ (pg_rto / 3)|round(0, 'ceil')|int }}

# timeout for DCS and PostgreSQL operation retries (in seconds). DCS or network issues shorter than this will not cause Patroni to demote the leader. Default value: 10
retry_timeout: {{ (pg_rto / 3)|round(0, 'ceil')|int }}

# the amount of time a primary is allowed to recover from failures before failover is triggered (in seconds), Max RTO: 2 loop wait + primary_start_timeout
primary_start_timeout: {{ (pg_rto / 3)|round(0, 'ceil')|int }}

pg_rpo

name: pg_rpo, type: int, level: C

recovery point objective in bytes, 1MiB at most by default

default values: 1048576, which will tolerate at most 1MiB data loss during failover.

when the primary is down and all replicas are lagged, you have to make a tough choice to trade off between Availability and Consistency:

  • Promote a replica to be the new primary and bring the system back online ASAP, with the price of an acceptable data loss (e.g. less than 1MB).
  • Wait for the primary to come back (which may never be) or human intervention to avoid any data loss.

You can use crit.yml conf template to ensure no data loss during failover, but it will sacrifice some performance.


pg_libs

name: pg_libs, type: string, level: C

shared preloaded libraries, pg_stat_statements,auto_explain by default.

They are two extensions that come with PostgreSQL, and it is strongly recommended to enable them.

For existing clusters, you can configure the shared_preload_libraries parameter of the cluster and apply it.

If you want to use TimescaleDB or Citus extensions, you need to add timescaledb or citus to this list. timescaledb and citus should be placed at the top of this list, for example:

citus,timescaledb,pg_stat_statements,auto_explain

Other extensions that need to be loaded can also be added to this list, such as pg_cron, pgml, etc.

Generally, citus and timescaledb have the highest priority and should be added to the top of the list.


pg_delay

name: pg_delay, type: interval, level: I

replications apply delay for standby cluster leader, default values: 0.

if this value is set to a positive value, the standby cluster leader will be delayed for this time before apply WAL changes.

Check delayed standby cluster for details.


pg_checksum

name: pg_checksum, type: bool, level: C

enable data checksum for postgres cluster? The v3.7.0 default value is true.

This parameter can only be set before PGSQL deployment. (but you can enable it manually later)

If pg_conf crit.yml template is used, data checksum is always enabled regardless of this parameter to ensure data integrity.


pg_pwd_enc

name: pg_pwd_enc, type: enum, level: C

password encryption algorithm: md5, scram-sha-256

default values: scram-sha-256, if you have compatibility issues with old clients, you can set it to md5 instead.

md5 encryption is deprecated!

The md5 option is deprecated but remains available in v3.7.0 for legacy clients; prefer scram-sha-256.


pg_encoding

name: pg_encoding, type: enum, level: C

database cluster encoding, UTF8 by default


pg_locale

name: pg_locale, type: enum, level: C

The locale set for PostgreSQL, default is C.

When configure detects that the current PG version is greater than or equal to 17, or the current system explicitly supports C.utf8, it will automatically configure this parameter to C.UTF-8.

When the PostgreSQL version is greater than or equal to 17, the C and C.UTF-8 configurations will use the PostgreSQL internal Locale Provider.

Unless you are very clear about what you are doing, it is strongly recommended to use the default C or C.UTF-8 configuration.


pg_lc_collate

name: pg_lc_collate, type: enum, level: C

The locale set for PostgreSQL, default is C.

When configure detects that the current PG version is greater than or equal to 17, or the current system explicitly supports C.utf8, it will automatically configure this parameter to C.UTF-8.

Unless you are very clear about what you are doing, it is strongly recommended to use the default C or C.UTF-8 configuration.

The parameter behaves like pg_locale, but for collate.


pg_lc_ctype

name: pg_lc_ctype, type: enum, level: C

The locale set for PostgreSQL, default is C.

When configure detects that the current PG version is greater than or equal to 17, or the current system explicitly supports C.utf8, it will automatically configure this parameter to C.UTF-8.

When the PostgreSQL version is greater than or equal to 17, the C and C.UTF-8 configurations will use the PostgreSQL internal Locale Provider.

This parameter behaves like pg_locale, but for ctype.

Unless you are very clear about what you are doing, it is strongly recommended to use the default C or C.UTF-8 configuration.


pgsodium_key

name: pgsodium_key, type: string, level: C

Default value is not defined, which will use the SHA256 hash of the pg_cluster as the key.

You can provide a custom pgsodium key, which should be a 64 hex digit string.

The key will be written to /pg/conf/pgsodium.key.


pgsodium_getkey_script

name: pgsodium_getkey_script, type: path, level: C

default value is pgsodium_getkey, which render the roles/pgsql/templates/pgsodium_getkey to /pg/bin/pgsodium_getkey.

The default getkey script will just read the pgsodium_key from /pg/conf/pgsodium.key, and return it. If your key is managed by external system like KMS, IAM, …, you can implement your own getkey script to fetch the key from there: examples.


PG_PROVISION

PG_BOOTSTRAP will bootstrap a new postgres cluster with patroni, while PG_PROVISION will create default objects in the cluster, including:

pg_provision: true                # provision postgres cluster after bootstrap
pg_init: pg-init                  # provision init script for cluster template, `pg-init` by default
pg_default_roles:                 # default roles and users in postgres cluster
  - { name: dbrole_readonly  ,login: false ,comment: role for global read-only access     }
  - { name: dbrole_offline   ,login: false ,comment: role for restricted read-only access }
  - { name: dbrole_readwrite ,login: false ,roles: [dbrole_readonly]               ,comment: role for global read-write access }
  - { name: dbrole_admin     ,login: false ,roles: [pg_monitor, dbrole_readwrite]  ,comment: role for object creation }
  - { name: postgres     ,superuser: true                                          ,comment: system superuser }
  - { name: replicator ,replication: true  ,roles: [pg_monitor, dbrole_readonly]   ,comment: system replicator }
  - { name: dbuser_dba   ,superuser: true  ,roles: [dbrole_admin]  ,pgbouncer: true ,pool_mode: session, pool_connlimit: 16 , comment: pgsql admin user }
  - { name: dbuser_monitor   ,roles: [pg_monitor, dbrole_readonly] ,pgbouncer: true ,parameters: {log_min_duration_statement: 1000 } ,pool_mode: session ,pool_connlimit: 8 ,comment: pgsql monitor user }
pg_default_privileges:            # default privileges when created by admin user
  - GRANT USAGE      ON SCHEMAS   TO dbrole_readonly
  - GRANT SELECT     ON TABLES    TO dbrole_readonly
  - GRANT SELECT     ON SEQUENCES TO dbrole_readonly
  - GRANT EXECUTE    ON FUNCTIONS TO dbrole_readonly
  - GRANT USAGE      ON SCHEMAS   TO dbrole_offline
  - GRANT SELECT     ON TABLES    TO dbrole_offline
  - GRANT SELECT     ON SEQUENCES TO dbrole_offline
  - GRANT EXECUTE    ON FUNCTIONS TO dbrole_offline
  - GRANT INSERT     ON TABLES    TO dbrole_readwrite
  - GRANT UPDATE     ON TABLES    TO dbrole_readwrite
  - GRANT DELETE     ON TABLES    TO dbrole_readwrite
  - GRANT USAGE      ON SEQUENCES TO dbrole_readwrite
  - GRANT UPDATE     ON SEQUENCES TO dbrole_readwrite
  - GRANT TRUNCATE   ON TABLES    TO dbrole_admin
  - GRANT REFERENCES ON TABLES    TO dbrole_admin
  - GRANT TRIGGER    ON TABLES    TO dbrole_admin
  - GRANT CREATE     ON SCHEMAS   TO dbrole_admin
pg_default_schemas: [ monitor ]   # default schemas to be created
pg_default_extensions:            # default extensions to be created
  - { name: pg_stat_statements ,schema: monitor }
  - { name: pgstattuple        ,schema: monitor }
  - { name: pg_buffercache     ,schema: monitor }
  - { name: pageinspect        ,schema: monitor }
  - { name: pg_prewarm         ,schema: monitor }
  - { name: pg_visibility      ,schema: monitor }
  - { name: pg_freespacemap    ,schema: monitor }
  - { name: postgres_fdw       ,schema: public  }
  - { name: file_fdw           ,schema: public  }
  - { name: btree_gist         ,schema: public  }
  - { name: btree_gin          ,schema: public  }
  - { name: pg_trgm            ,schema: public  }
  - { name: intagg             ,schema: public  }
  - { name: intarray           ,schema: public  }
  - { name: pg_repack }
pg_reload: true                   # reload postgres/pgbouncer/vip after conf changes
pg_default_hba_rules:             # postgres default host-based authentication rules
  - {user: '${dbsu}'    ,db: all         ,addr: local     ,auth: ident ,title: 'dbsu access via local os user ident'  }
  - {user: '${dbsu}'    ,db: replication ,addr: local     ,auth: ident ,title: 'dbsu replication from local os ident' }
  - {user: '${repl}'    ,db: replication ,addr: localhost ,auth: pwd   ,title: 'replicator replication from localhost'}
  - {user: '${repl}'    ,db: replication ,addr: intra     ,auth: pwd   ,title: 'replicator replication from intranet' }
  - {user: '${repl}'    ,db: postgres    ,addr: intra     ,auth: pwd   ,title: 'replicator postgres db from intranet' }
  - {user: '${monitor}' ,db: all         ,addr: localhost ,auth: pwd   ,title: 'monitor from localhost with password' }
  - {user: '${monitor}' ,db: all         ,addr: infra     ,auth: pwd   ,title: 'monitor from infra host with password'}
  - {user: '${admin}'   ,db: all         ,addr: infra     ,auth: ssl   ,title: 'admin @ infra nodes with pwd & ssl'   }
  - {user: '${admin}'   ,db: all         ,addr: world     ,auth: ssl   ,title: 'admin @ everywhere with ssl & pwd'    }
  - {user: '+dbrole_readonly',db: all    ,addr: localhost ,auth: pwd   ,title: 'pgbouncer read/write via local socket'}
  - {user: '+dbrole_readonly',db: all    ,addr: intra     ,auth: pwd   ,title: 'read/write biz user via password'     }
  - {user: '+dbrole_offline' ,db: all    ,addr: intra     ,auth: pwd   ,title: 'allow etl offline tasks from intranet'}
pgb_default_hba_rules:            # pgbouncer default host-based authentication rules
  - {user: '${dbsu}'    ,db: pgbouncer   ,addr: local     ,auth: peer  ,title: 'dbsu local admin access with os ident'}
  - {user: 'all'        ,db: all         ,addr: localhost ,auth: pwd   ,title: 'allow all user local access with pwd' }
  - {user: '${monitor}' ,db: pgbouncer   ,addr: intra     ,auth: pwd   ,title: 'monitor access via intranet with pwd' }
  - {user: '${monitor}' ,db: all         ,addr: world     ,auth: deny  ,title: 'reject all other monitor access addr' }
  - {user: '${admin}'   ,db: all         ,addr: intra     ,auth: pwd   ,title: 'admin access via intranet with pwd'   }
  - {user: '${admin}'   ,db: all         ,addr: world     ,auth: deny  ,title: 'reject all other admin access addr'   }
  - {user: 'all'        ,db: all         ,addr: intra     ,auth: pwd   ,title: 'allow all user intra access with pwd' }

pg_provision

name: pg_provision, type: bool, level: C

provision postgres cluster after bootstrap, default value is true.

If disabled, postgres cluster will not be provisioned after bootstrap.


pg_init

name: pg_init, type: string, level: G/C

Provision init script for cluster template, pg-init by default, which is located in roles/pgsql/templates/pg-init

You can add your own logic in the init script, or provide a new one in templates/ and set pg_init to the new script name.


pg_default_roles

name: pg_default_roles, type: role[], level: G/C

default roles and users in postgres cluster.

Pigsty has a built-in role system, check PGSQL Access Control for details.

pg_default_roles:                 # default roles and users in postgres cluster
  - { name: dbrole_readonly  ,login: false ,comment: role for global read-only access     }
  - { name: dbrole_offline   ,login: false ,comment: role for restricted read-only access }
  - { name: dbrole_readwrite ,login: false ,roles: [dbrole_readonly]               ,comment: role for global read-write access }
  - { name: dbrole_admin     ,login: false ,roles: [pg_monitor, dbrole_readwrite]  ,comment: role for object creation }
  - { name: postgres     ,superuser: true                                          ,comment: system superuser }
  - { name: replicator ,replication: true  ,roles: [pg_monitor, dbrole_readonly]   ,comment: system replicator }
  - { name: dbuser_dba   ,superuser: true  ,roles: [dbrole_admin]  ,pgbouncer: true ,pool_mode: session, pool_connlimit: 16 , comment: pgsql admin user }
  - { name: dbuser_monitor   ,roles: [pg_monitor, dbrole_readonly] ,pgbouncer: true ,parameters: {log_min_duration_statement: 1000 } ,pool_mode: session ,pool_connlimit: 8 ,comment: pgsql monitor user }

pg_default_privileges

name: pg_default_privileges, type: string[], level: G/C

default privileges for each databases:

pg_default_privileges:            # default privileges when created by admin user
  - GRANT USAGE      ON SCHEMAS   TO dbrole_readonly
  - GRANT SELECT     ON TABLES    TO dbrole_readonly
  - GRANT SELECT     ON SEQUENCES TO dbrole_readonly
  - GRANT EXECUTE    ON FUNCTIONS TO dbrole_readonly
  - GRANT USAGE      ON SCHEMAS   TO dbrole_offline
  - GRANT SELECT     ON TABLES    TO dbrole_offline
  - GRANT SELECT     ON SEQUENCES TO dbrole_offline
  - GRANT EXECUTE    ON FUNCTIONS TO dbrole_offline
  - GRANT INSERT     ON TABLES    TO dbrole_readwrite
  - GRANT UPDATE     ON TABLES    TO dbrole_readwrite
  - GRANT DELETE     ON TABLES    TO dbrole_readwrite
  - GRANT USAGE      ON SEQUENCES TO dbrole_readwrite
  - GRANT UPDATE     ON SEQUENCES TO dbrole_readwrite
  - GRANT TRUNCATE   ON TABLES    TO dbrole_admin
  - GRANT REFERENCES ON TABLES    TO dbrole_admin
  - GRANT TRIGGER    ON TABLES    TO dbrole_admin
  - GRANT CREATE     ON SCHEMAS   TO dbrole_admin

Pigsty has a built-in privileges based on the default role system, check PGSQL Privileges for details.


pg_default_schemas

name: pg_default_schemas, type: string[], level: G/C

default schemas to be created, default values is: [ monitor ], which will create a monitor schema on all databases.


pg_default_extensions

name: pg_default_extensions, type: extension[], level: G/C

default extensions to be created, default value:

pg_default_extensions: # default extensions to be created
  - { name: pg_stat_statements ,schema: monitor }
  - { name: pgstattuple        ,schema: monitor }
  - { name: pg_buffercache     ,schema: monitor }
  - { name: pageinspect        ,schema: monitor }
  - { name: pg_prewarm         ,schema: monitor }
  - { name: pg_visibility      ,schema: monitor }
  - { name: pg_freespacemap    ,schema: monitor }
  - { name: postgres_fdw       ,schema: public  }
  - { name: file_fdw           ,schema: public  }
  - { name: btree_gist         ,schema: public  }
  - { name: btree_gin          ,schema: public  }
  - { name: pg_trgm            ,schema: public  }
  - { name: intagg             ,schema: public  }
  - { name: intarray           ,schema: public  }
  - { name: pg_repack }

The only 3rd party extension is pg_repack, which is important for database maintenance, all other extensions are built-in postgres contrib extensions.

Monitor related extensions are installed in monitor schema, which is created by pg_default_schemas.


pg_reload

name: pg_reload, type: bool, level: A

reload postgres after hba changes, default value is true

This is useful when you want to check before applying HBA changes, set it to false to disable reload.


pg_default_hba_rules

name: pg_default_hba_rules, type: hba[], level: G/C

postgres default host-based authentication rules, array of hba rule object.

default value provides a fair enough security level for common scenarios, check PGSQL Authentication for details.

pg_default_hba_rules:             # postgres default host-based authentication rules
  - {user: '${dbsu}'    ,db: all         ,addr: local     ,auth: ident ,title: 'dbsu access via local os user ident'  }
  - {user: '${dbsu}'    ,db: replication ,addr: local     ,auth: ident ,title: 'dbsu replication from local os ident' }
  - {user: '${repl}'    ,db: replication ,addr: localhost ,auth: pwd   ,title: 'replicator replication from localhost'}
  - {user: '${repl}'    ,db: replication ,addr: intra     ,auth: pwd   ,title: 'replicator replication from intranet' }
  - {user: '${repl}'    ,db: postgres    ,addr: intra     ,auth: pwd   ,title: 'replicator postgres db from intranet' }
  - {user: '${monitor}' ,db: all         ,addr: localhost ,auth: pwd   ,title: 'monitor from localhost with password' }
  - {user: '${monitor}' ,db: all         ,addr: infra     ,auth: pwd   ,title: 'monitor from infra host with password'}
  - {user: '${admin}'   ,db: all         ,addr: infra     ,auth: ssl   ,title: 'admin @ infra nodes with pwd & ssl'   }
  - {user: '${admin}'   ,db: all         ,addr: world     ,auth: ssl   ,title: 'admin @ everywhere with ssl & pwd'    }
  - {user: '+dbrole_readonly',db: all    ,addr: localhost ,auth: pwd   ,title: 'pgbouncer read/write via local socket'}
  - {user: '+dbrole_readonly',db: all    ,addr: intra     ,auth: pwd   ,title: 'read/write biz user via password'     }
  - {user: '+dbrole_offline' ,db: all    ,addr: intra     ,auth: pwd   ,title: 'allow etl offline tasks from intranet'}

pgb_default_hba_rules

name: pgb_default_hba_rules, type: hba[], level: G/C

pgbouncer default host-based authentication rules, array or hba rule object.

default value provides a fair enough security level for common scenarios, check PGSQL Authentication for details.

pgb_default_hba_rules:            # pgbouncer default host-based authentication rules
  - {user: '${dbsu}'    ,db: pgbouncer   ,addr: local     ,auth: peer  ,title: 'dbsu local admin access with os ident'}
  - {user: 'all'        ,db: all         ,addr: localhost ,auth: pwd   ,title: 'allow all user local access with pwd' }
  - {user: '${monitor}' ,db: pgbouncer   ,addr: intra     ,auth: pwd   ,title: 'monitor access via intranet with pwd' }
  - {user: '${monitor}' ,db: all         ,addr: world     ,auth: deny  ,title: 'reject all other monitor access addr' }
  - {user: '${admin}'   ,db: all         ,addr: intra     ,auth: pwd   ,title: 'admin access via intranet with pwd'   }
  - {user: '${admin}'   ,db: all         ,addr: world     ,auth: deny  ,title: 'reject all other admin access addr'   }
  - {user: 'all'        ,db: all         ,addr: intra     ,auth: pwd   ,title: 'allow all user intra access with pwd' }

PG_BACKUP

This section defines variables for pgBackRest, which is used for PGSQL PITR (Point-In-Time-Recovery).

Check PGSQL Backup & PITR for details.

pgbackrest_enabled: true          # enable pgbackrest on pgsql host?
pgbackrest_clean: true            # remove pg backup data during init?
pgbackrest_log_dir: /pg/log/pgbackrest # pgbackrest log dir, `/pg/log/pgbackrest` by default
pgbackrest_method: local          # pgbackrest repo method: local,minio,[user-defined...]
pgbackrest_init_backup: true      # take a full backup after pgbackrest is initialized?
pgbackrest_repo:                  # pgbackrest repo: https://pgbackrest.org/configuration.html#section-repository
  local:                          # default pgbackrest repo with local posix fs
    path: /pg/backup              # local backup directory, `/pg/backup` by default
    retention_full_type: count    # retention full backups by count
    retention_full: 2             # keep 2, at most 3 full backups when using local fs repo
  minio:                          # optional minio repo for pgbackrest
    type: s3                      # minio is s3-compatible, so s3 is used
    s3_endpoint: sss.pigsty       # minio endpoint domain name, `sss.pigsty` by default
    s3_region: us-east-1          # minio region, us-east-1 by default, useless for minio
    s3_bucket: pgsql              # minio bucket name, `pgsql` by default
    s3_key: pgbackrest            # minio user access key for pgbackrest
    s3_key_secret: S3User.Backup  # minio user secret key for pgbackrest
    s3_uri_style: path            # use path style uri for minio rather than host style
    path: /pgbackrest             # minio backup path, default is `/pgbackrest`
    storage_port: 9000            # minio port, 9000 by default
    storage_ca_file: /etc/pki/ca.crt  # minio ca file path, `/etc/pki/ca.crt` by default
    block: y                      # Enable block incremental backup
    bundle: y                     # bundle small files into a single file
    bundle_limit: 20MiB           # Limit for file bundles, 20MiB for object storage
    bundle_size: 128MiB           # Target size for file bundles, 128MiB for object storage
    cipher_type: aes-256-cbc      # enable AES encryption for remote backup repo
    cipher_pass: pgBackRest       # AES encryption password, default is 'pgBackRest'
    retention_full_type: time     # retention full backup by time on minio repo
    retention_full: 14            # keep full backup for the last 14 days

pgbackrest_enabled

name: pgbackrest_enabled, type: bool, level: C

enable pgBackRest on pgsql host? default value is true

When using the local file system backup repository (local), only the primary instance of the cluster will actually enable pgbackrest. Other instances will only initialize an empty repository.


pgbackrest_clean

name: pgbackrest_clean, type: bool, level: C

remove pg backup data during init? default value is true


pgbackrest_log_dir

name: pgbackrest_log_dir, type: path, level: C

pgBackRest log dir, /pg/log/pgbackrest by default, which is referenced by promtail the logging agent.


pgbackrest_method

name: pgbackrest_method, type: enum, level: C

pgBackRest repo method: local, minio, or other user-defined methods, local by default

This parameter is used to determine which repo to use for pgBackRest, all available repo methods are defined in pgbackrest_repo.

Pigsty will use local backup repo by default, which will create a backup repo on primary instance’s /pg/backup directory. The underlying storage is specified by pg_fs_backup.


pgbackrest_init_backup

name: pgbackrest_init_backup, type: bool, level: C

Take a full backup after pgBackRest is initialized? default value is true.

An initial pgbackrest backup is created after repo init if:

  • pgbackrest_init_backup is true (and pgbackrest_enabled is true of course)
  • The /etc/pgbackrest/initial.done marker file doesn’t exist (will be created after the initial backup is done).

If you don’t want to take an initial full backup at all, just set this parameter tofalse.


pgbackrest_repo

name: pgbackrest_repo, type: dict, level: G/C

pgBackRest repo document: https://pgbackrest.org/configuration.html#section-repository

default value includes two repo methods: local and minio, which are defined as follows:

pgbackrest_repo:                  # pgbackrest repo: https://pgbackrest.org/configuration.html#section-repository
  local:                          # default pgbackrest repo with local posix fs
    path: /pg/backup              # local backup directory, `/pg/backup` by default
    retention_full_type: count    # retention full backups by count
    retention_full: 2             # keep 2, at most 3 full backups when using local fs repo
  minio:                          # optional minio repo for pgbackrest
    type: s3                      # minio is s3-compatible, so s3 is used
    s3_endpoint: sss.pigsty       # minio endpoint domain name, `sss.pigsty` by default
    s3_region: us-east-1          # minio region, us-east-1 by default, useless for minio
    s3_bucket: pgsql              # minio bucket name, `pgsql` by default
    s3_key: pgbackrest            # minio user access key for pgbackrest
    s3_key_secret: S3User.Backup  # minio user secret key for pgbackrest
    s3_uri_style: path            # use path style uri for minio rather than host style
    path: /pgbackrest             # minio backup path, default is `/pgbackrest`
    storage_port: 9000            # minio port, 9000 by default
    storage_ca_file: /etc/pki/ca.crt  # minio ca file path, `/etc/pki/ca.crt` by default
    block: y                      # Enable block incremental backup
    bundle: y                     # bundle small files into a single file
    bundle_limit: 20MiB           # Limit for file bundles, 20MiB for object storage
    bundle_size: 128MiB           # Target size for file bundles, 128MiB for object storage
    cipher_type: aes-256-cbc      # enable AES encryption for remote backup repo
    cipher_pass: pgBackRest       # AES encryption password, default is 'pgBackRest'
    retention_full_type: time     # retention full backup by time on minio repo
    retention_full: 14            # keep full backup for the last 14 days

You can define a new backup repository, for example, using AWS S3, GCP or another cloud provider’s S3-compatible storage service.

In the backup repository definition parameters, you can use ${pg_cluster} variable to reference the cluster name, for example, as part of the backup path or encryption key. But if you have cross-cluster PITR requirements, you should keep the backup repository path and encryption key the same.


PG_ACCESS

This section is about exposing PostgreSQL service to the outside world: including:

  • Connection Pooling with pgbouncer
  • Exposing different PostgreSQL services on different ports with haproxy
  • Bind an optional L2 VIP to the primary instance with vip-manager
  • Register cluster/instance DNS records with to dnsmasq on infra nodes
pgbouncer_enabled: true           # if disabled, pgbouncer will not be launched on pgsql host
pgbouncer_port: 6432              # pgbouncer listen port, 6432 by default
pgbouncer_log_dir: /pg/log/pgbouncer  # pgbouncer log dir, `/pg/log/pgbouncer` by default
pgbouncer_auth_query: false       # query postgres to retrieve unlisted business users?
pgbouncer_poolmode: transaction   # pooling mode: transaction,session,statement, transaction by default
pgbouncer_sslmode: disable        # pgbouncer client ssl mode, disable by default

pg_weight: 100          #INSTANCE # relative load balance weight in service, 100 by default, 0-255
pg_default_service_dest: pgbouncer # default service destination if svc.dest='default'
pg_default_services:              # postgres default service definitions
  - { name: primary ,port: 5433 ,dest: default  ,check: /primary   ,selector: "[]" }
  - { name: replica ,port: 5434 ,dest: default  ,check: /read-only ,selector: "[]" , backup: "[? pg_role == `primary` || pg_role == `offline` ]" }
  - { name: default ,port: 5436 ,dest: postgres ,check: /primary   ,selector: "[]" }
  - { name: offline ,port: 5438 ,dest: postgres ,check: /replica   ,selector: "[? pg_role == `offline` || pg_offline_query ]" , backup: "[? pg_role == `replica` && !pg_offline_query]"}
pg_vip_enabled: false             # enable a l2 vip for pgsql primary? false by default
pg_vip_address: 127.0.0.1/24      # vip address in `<ipv4>/<mask>` format, require if vip is enabled
pg_vip_interface: eth0            # vip network interface to listen, eth0 by default
pg_dns_suffix: ''                 # pgsql dns suffix, '' by default
pg_dns_target: auto               # auto, primary, vip, none, or ad hoc ip

pgbouncer_enabled

name: pgbouncer_enabled, type: bool, level: C

default value is true, if disabled, pgbouncer will not be launched on pgsql host


pgbouncer_port

name: pgbouncer_port, type: port, level: C

pgbouncer listen port, 6432 by default


pgbouncer_log_dir

name: pgbouncer_log_dir, type: path, level: C

pgbouncer log dir, /pg/log/pgbouncer by default, referenced by promtail the logging agent.


pgbouncer_auth_query

name: pgbouncer_auth_query, type: bool, level: C

query postgres to retrieve unlisted business users? default value is false

If enabled, pgbouncer user will be authenticated against postgres databases with SELECT username, password FROM monitor.pgbouncer_auth($1), otherwise, only the users with pgbouncer: true will be allowed to connect to pgbouncer.


pgbouncer_poolmode

name: pgbouncer_poolmode, type: enum, level: C

Pgbouncer pooling mode: transaction, session, statement, transaction by default

  • session: Session-level pooling with the best compatibility.
  • transaction: Transaction-level pooling with better performance (lots of small conns), could break some session level features such as notify/listen, etc…
  • statements: Statement-level pooling which is used for simple read-only queries.

If your application has some compatibility issues with pgbouncer, you can try to change this value to session instead.


pgbouncer_sslmode

name: pgbouncer_sslmode, type: enum, level: C

pgbouncer client ssl mode, disable by default

default values: disable, beware that this may have a huge performance impact on your pgbouncer.

  • disable: Plain TCP. If a client requests TLS, it’s ignored. Default.
  • allow: If a client requests TLS, it is used. If not, plain TCP is used. If the client presents a client certificate, it is not validated.
  • prefer: Same as allow.
  • require: Client must use TLS. If not, the client connection is rejected. If the client presents a client certificate, it is not validated.
  • verify-ca: Client must use TLS with valid client certificate.
  • verify-full: Same as verify-ca.

pgbouncer_ignore_param

name: pgbouncer_ignore_param, type: string[], level: G/C

default values: [ extra_float_digits, application_name, TimeZone, DateStyle, IntervalStyle, search_path ]

This will be used as value of ignore_startup_parameters in pgbouncer.


pg_weight

name: pg_weight, type: int, level: G

relative load balance weight in service, 100 by default, 0~255

default values: 100. you have to define it at instance vars, and reload-service to take effect.


pg_service_provider

name: pg_service_provider, type: string, level: G/C

dedicate haproxy node group name, or empty string for local nodes by default.

If specified, PostgreSQL Services will be registered to the dedicated haproxy node group instead of this pgsql cluster nodes.

Do remember to allocate unique ports on dedicated haproxy nodes for each service!

For example, if we define the following parameters on 3-node pg-test cluster:

pg_service_provider: infra       # use load balancer on group `infra`
pg_default_services:             # alloc port 10001 and 10002 for pg-test primary/replica service
  - { name: primary ,port: 10001 ,dest: postgres  ,check: /primary   ,selector: "[]" }
  - { name: replica ,port: 10002 ,dest: postgres  ,check: /read-only ,selector: "[]" , backup: "[? pg_role == `primary` || pg_role == `offline` ]" }

pg_default_service_dest

name: pg_default_service_dest, type: enum, level: G/C

When defining a service, if svc.dest= default, this parameter will be used as the default value.

default values: pgbouncer, means 5433 the primary service and 5434 the replicas service will route traffic to pgbouncer by default.

If you don’t want to use pgbouncer, set it to postgres instead. traffic will be routed to postgres directly.


pg_default_services

name: pg_default_services, type: service[], level: G/C

postgres default service definitions

default value is four default services definitions, which are explained in PGSQL Service

pg_default_services:               # postgres default service definitions
  - { name: primary ,port: 5433 ,dest: default  ,check: /primary   ,selector: "[]" }
  - { name: replica ,port: 5434 ,dest: default  ,check: /read-only ,selector: "[]" , backup: "[? pg_role == `primary` || pg_role == `offline` ]" }
  - { name: default ,port: 5436 ,dest: postgres ,check: /primary   ,selector: "[]" }
  - { name: offline ,port: 5438 ,dest: postgres ,check: /replica   ,selector: "[? pg_role == `offline` || pg_offline_query ]" , backup: "[? pg_role == `replica` && !pg_offline_query]"}

pg_vip_enabled

name: pg_vip_enabled, type: bool, level: C

enable a l2 vip for pgsql primary?

default value is false, means no L2 VIP is created for this cluster.

L2 VIP can only be used in the same L2 network, which may incur extra restrictions on your network topology.


pg_vip_address

name: pg_vip_address, type: cidr4, level: C

vip address in <ipv4>/<mask> format, if vip is enabled, this parameter is required.

default values: 127.0.0.1/24. This value is consist of two parts: ipv4 and mask, separated by /.


pg_vip_interface

name: pg_vip_interface, type: string, level: C/I

vip network interface to listen, eth0 by default.

It should be the same primary intranet interface of your node, which is the IP address you used in the inventory file.

If your nodes have different interface, you can override it on instance vars:

pg-test:
    hosts:
        10.10.10.11: {pg_seq: 1, pg_role: replica ,pg_vip_interface: eth0 }
        10.10.10.12: {pg_seq: 2, pg_role: primary ,pg_vip_interface: eth1 }
        10.10.10.13: {pg_seq: 3, pg_role: replica ,pg_vip_interface: eth2 }
    vars:
        pg_vip_enabled: true          # enable L2 VIP for this cluster, bind to primary instance by default
        pg_vip_address: 10.10.10.3/24 # the L2 network CIDR: 10.10.10.0/24, the vip address: 10.10.10.3
        # pg_vip_interface: eth1      # if your node have non-uniform interface, you can define it here

pg_dns_suffix

name: pg_dns_suffix, type: string, level: C

pgsql dns suffix, empty string by default, cluster DNS name is defined as {{ pg_cluster }}{{ pg_dns_suffix }}

For example, if you set pg_dns_suffix to .db.vip.company.tld for cluster pg-test, then the cluster DNS name will be pg-test.db.vip.company.tld


pg_dns_target

name: pg_dns_target, type: enum, level: C

Could be: auto, primary, vip, none, or an ad hoc ip address, which will be the target IP address of cluster DNS record.

default values: auto , which will bind to pg_vip_address if pg_vip_enabled, or fallback to cluster primary instance ip address.

  • vip: bind to pg_vip_address
  • primary: resolve to cluster primary instance ip address
  • auto: resolve to pg_vip_address if pg_vip_enabled, or fallback to cluster primary instance ip address.
  • none: do not bind to any ip address
  • <ipv4>: bind to the given IP address

PG_MONITOR

pg_exporter_enabled: true              # enable pg_exporter on pgsql hosts?
pg_exporter_config: pg_exporter.yml    # pg_exporter configuration file name
pg_exporter_cache_ttls: '1,10,60,300'  # pg_exporter collector ttl stage in seconds, '1,10,60,300' by default
pg_exporter_port: 9630                 # pg_exporter listen port, 9630 by default
pg_exporter_params: 'sslmode=disable'  # extra url parameters for pg_exporter dsn
pg_exporter_url: ''                    # overwrite auto-generate pg dsn if specified
pg_exporter_auto_discovery: true       # enable auto database discovery? enabled by default
pg_exporter_exclude_database: 'template0,template1,postgres' # csv of databases that WILL NOT be monitored during auto-discovery
pg_exporter_include_database: ''       # csv of databases that WILL BE monitored during auto-discovery
pg_exporter_connect_timeout: 200       # pg_exporter connect timeout in ms, 200 by default
pg_exporter_options: ''                # overwrite extra options for pg_exporter
pgbouncer_exporter_enabled: true       # enable pgbouncer_exporter on pgsql hosts?
pgbouncer_exporter_port: 9631          # pgbouncer_exporter listen port, 9631 by default
pgbouncer_exporter_url: ''             # overwrite auto-generate pgbouncer dsn if specified
pgbouncer_exporter_options: ''         # overwrite extra options for pgbouncer_exporter
pgbackrest_exporter_enabled: true      # enable pgbackrest_exporter on pgsql hosts?
pgbackrest_exporter_port: 9854         # pgbackrest_exporter listen port, 9854 by default
pgbackrest_exporter_options: ''        # overwrite extra options for pgbackrest_exporter

pg_exporter_enabled

name: pg_exporter_enabled, type: bool, level: C

enable pg_exporter on pgsql hosts?

default value is true, if you don’t want to install pg_exporter, set it to false.


pg_exporter_config

name: pg_exporter_config, type: string, level: C

pg_exporter configuration file name, used by pg_exporter & pgbouncer_exporter

default values: pg_exporter.yml, if you want to use a custom configuration file, you can specify its relative path here.

Your config file should be placed in files/<filename>.yml. For example, if you want to monitor a remote PolarDB instance, you can use the sample config: files/polar_exporter.yml.


pg_exporter_cache_ttls

name: pg_exporter_cache_ttls, type: string, level: C

pg_exporter collector ttl stage in seconds, 1,10,60,300 by default

default values: 1,10,60,300, which will use 1s, 10s, 60s, 300s for different metric collectors.

ttl_fast: "{{ pg_exporter_cache_ttls.split(',')[0]|int }}"         # critical queries
ttl_norm: "{{ pg_exporter_cache_ttls.split(',')[1]|int }}"         # common queries
ttl_slow: "{{ pg_exporter_cache_ttls.split(',')[2]|int }}"         # slow queries (e.g table size)
ttl_slowest: "{{ pg_exporter_cache_ttls.split(',')[3]|int }}"      # ver slow queries (e.g bloat)

This should be set in pair with prometheus_scrape_interval

  • fast : 1~10s, critical metrics, never cache
  • norm : same as prometheus scrape internal
  • slow : slow and bulky metrics like object metrics
  • slowest : very slow metrics like table size, bloat rate

pg_exporter_port

name: pg_exporter_port, type: port, level: C

pg_exporter listen port, 9630 by default


pg_exporter_params

name: pg_exporter_params, type: string, level: C

extra url parameters for pg_exporter dsn

default values: sslmode=disable, which will disable SSL for monitoring connection (since it’s local unix socket by default)


pg_exporter_url

name: pg_exporter_url, type: pgurl, level: C

overwrite auto-generate pg dsn if specified

default value is empty string, If specified, it will be used as the pg_exporter dsn instead of constructing from other parameters:

This could be useful if you want to monitor a remote pgsql instance, or you want to use a different user/password for monitoring.

'postgres://{{ pg_monitor_username }}:{{ pg_monitor_password }}@{{ pg_host }}:{{ pg_port }}/postgres{% if pg_exporter_params != '' %}?{{ pg_exporter_params }}{% endif %}'

pg_exporter_auto_discovery

name: pg_exporter_auto_discovery, type: bool, level: C

enable auto database discovery? enabled by default

default value is true, which will auto-discover all databases on the postgres server and spawn a new pg_exporter connection for each database.


pg_exporter_exclude_database

name: pg_exporter_exclude_database, type: string, level: C

csv of databases that WILL NOT be monitored during auto-discovery

default values: template0,template1,postgres, which will be excluded for database auto discovery.


pg_exporter_include_database

name: pg_exporter_include_database, type: string, level: C

csv of databases that WILL BE monitored during auto-discovery

the default value is empty string. If set, only the databases in this list will be monitored during auto discovery.


pg_exporter_connect_timeout

name: pg_exporter_connect_timeout, type: int, level: C

pg_exporter connect timeout in ms, 200 by default

default values: 200ms , which is enough for most cases.

If your remote pgsql server is in another continent, you may want to increase this value to avoid connection timeout.


pg_exporter_options

name: pg_exporter_options, type: arg, level: C

overwrite extra options for pg_exporter

the default value is empty string, which will fall back the following default options:

{% if pg_exporter_port != '' %}
PG_EXPORTER_OPTS='--web.listen-address=:{{ pg_exporter_port }} {{ pg_exporter_options }}'
{% else %}
PG_EXPORTER_OPTS='--web.listen-address=:{{ pg_exporter_port }} --log.level=info'
{% endif %}

If you want to customize logging options or other pg_exporter options, you can set it here.


pgbouncer_exporter_enabled

name: pgbouncer_exporter_enabled, type: bool, level: C

enable pgbouncer_exporter on pgsql hosts?

default value is true, which will enable pg_exporter for pgbouncer connection pooler.


pgbouncer_exporter_port

name: pgbouncer_exporter_port, type: port, level: C

pgbouncer_exporter listen port, 9631 by default


pgbouncer_exporter_url

name: pgbouncer_exporter_url, type: pgurl, level: C

overwrite auto-generate pgbouncer dsn if specified

the default value is empty string, If specified, it will be used as the pgbouncer_exporter dsn instead of constructing from other parameters:

'postgres://{{ pg_monitor_username }}:{{ pg_monitor_password }}@:{{ pgbouncer_port }}/pgbouncer?host={{ pg_localhost }}&sslmode=disable'

This could be useful if you want to monitor a remote pgbouncer instance, or you want to use a different user/password for monitoring.


pgbouncer_exporter_options

name: pgbouncer_exporter_options, type: arg, level: C

overwrite extra options for pgbouncer_exporter, default value is empty string.

the default value is empty string, which will fall back the following default options:

{% if pgbouncer_exporter_options != '' %}
PG_EXPORTER_OPTS='--web.listen-address=:{{ pgbouncer_exporter_port }} {{ pgbouncer_exporter_options }}'
{% else %}
PG_EXPORTER_OPTS='--web.listen-address=:{{ pgbouncer_exporter_port }} --log.level=info'
{% endif %}

If you want to customize logging options or other pgbouncer_exporter options, you can set it here. but do not overwrite pgbouncer_exporter_port here.


pgbackrest_exporter_enabled

name: pgbackrest_exporter_enabled, type: bool, level: C

enable pgbackrest_exporter on pgsql hosts? default value is true

If pgbackrest_enabled is false, this parameter will be short-circuited and disabled.


pgbackrest_exporter_port

name: pgbackrest_exporter_port, type: port, level: C

pgbackrest_exporter listen port, 9854 by default


pgbackrest_exporter_options

name: pgbackrest_exporter_options, type: arg, level: C

extra cli args for pgbackrest_exporter, default value is empty string "".


PG_REMOVE

These flags control the pgsql-rm.yml playbook and match roles/pg_remove/defaults/main.yml in v3.7.0.

pg_safeguard: false               # abort removal when explicitly enabled
pg_rm_data: true                  # remove postgres data during removal
pg_rm_backup: true                # remove primary pgBackRest backup during removal
pg_rm_pkg: true                   # uninstall postgres packages during removal

pg_safeguard

name: pg_safeguard, type: bool, level: G/C/A

When true, the pgsql-rm.yml playbook aborts before changing the cluster. The v3.7.0 default is false.


pg_rm_data

name: pg_rm_data, type: bool, level: G/C/A

Remove PostgreSQL data during removal. The default is true; set it to false to preserve the data directories.


pg_rm_backup

name: pg_rm_backup, type: bool, level: G/C/A

Remove the pgBackRest repository when removing a primary instance. The default is true; set it to false to preserve backups.


pg_rm_pkg

name: pg_rm_pkg, type: bool, level: G/C/A

Uninstall PostgreSQL and extension packages during removal. The v3.7.0 role default is true; set it to false to keep installed packages.

4 - Administration

run administrative tasks

How to maintain an existing PostgreSQL cluster with Pigsty?

Here are some SOP for common pgsql admin tasks


Cheatsheet

PGSQL playbooks and shortcuts:

bin/pgsql-add   <cls>                   # create pgsql cluster <cls>
bin/pgsql-user  <cls> <username>        # create pg user <username> on <cls>
bin/pgsql-db    <cls> <dbname>          # create pg database <dbname> on <cls>
bin/pgsql-svc   <cls> [...ip]           # reload pg service of cluster <cls>
bin/pgsql-hba   <cls> [...ip]           # reload postgres/pgbouncer HBA rules of cluster <cls>
bin/pgsql-add   <cls> [...ip]           # append replicas for cluster <cls>
bin/pgsql-rm    <cls> [...ip]           # remove replicas from cluster <cls>
bin/pgsql-rm    <cls>                   # remove pgsql cluster <cls>

Patroni admin command and shortcuts:

pg list        <cls>                    # print cluster info
pg edit-config <cls>                    # edit cluster config
pg reload      <cls> [ins]              # reload cluster config
pg restart     <cls> [ins]              # restart pgsql cluster
pg reinit      <cls> [ins]              # reinit cluster members
pg pause       <cls>                    # entering maintenance mode (no auto failover)
pg resume      <cls>                    # exiting maintenance mode
pg switchover  <cls>                    # switchover on cluster <cls>
pg failover    <cls>                    # failover on cluster <cls>

pgBackRest backup & restore command and shortcuts:

pb info                                 # print pgbackrest repo info
pg-backup                               # make a backup, incr, or full backup if necessary
pg-backup full                          # make a full backup
pg-backup diff                          # make a differential backup
pg-backup incr                          # make a incremental backup
./pgsql-pitr.yml -e '{"pg_pitr": { "time": "2025-07-13 10:00:00+00" }}'
./pgsql-pitr.yml -e '{"pg_pitr": { "name": "shit_incoming" }}'
./pgsql-pitr.yml -e '{"pg_pitr": { "xid": "250000", exclusive: true }}'
./pgsql-pitr.yml -e '{"pg_pitr": { "lsn": "0/4001C80", timeline: "1" }}'

Systemd components quick reference

systemctl stop patroni                  # start stop restart reload
systemctl stop pgbouncer                # start stop restart reload
systemctl stop pg_exporter              # start stop restart reload
systemctl stop pgbouncer_exporter       # start stop restart reload
systemctl stop node_exporter            # start stop restart
systemctl stop haproxy                  # start stop restart reload
systemctl stop vip-manager              # start stop restart reload
systemctl stop postgres                 # only when patroni_mode == 'remove'

Create Cluster

To create a new Postgres cluster, define it in the inventory first, then init with:

bin/node-add <cls>                # init nodes for cluster <cls>           # ./node.yml  -l <cls>
bin/pgsql-add <cls>               # init pgsql instances of cluster <cls>  # ./pgsql.yml -l <cls>

Beware, perform bin/node-add first, then bin/pgsql-add, PGSQL works on managed nodes only.


Create User

To create a new business user on the existing Postgres cluster, add user definition to all.children.<cls>.pg_users, then create the user as follows:

bin/pgsql-user <cls> <username>   # ./pgsql-user.yml -l <cls> -e username=<username>

Create Database

To create a new database user on the existing Postgres cluster, add database definition to all.children.<cls>.pg_databases, then create the database as follows:

bin/pgsql-db <cls> <dbname>       # ./pgsql-db.yml -l <cls> -e dbname=<dbname>

Note: If the database has specified an owner, the user should already exist, or you’ll have to Create User first.


Reload Service

Services are exposed access point served by HAProxy.

This task is used when cluster membership has changed, e.g., append/remove replicas, switchover/failover / exposing new service or updating existing service’s config (e.g., LB Weight)

To create new services or reload existing services on entire proxy cluster or specific instances:

bin/pgsql-svc <cls>               # pgsql.yml -l <cls> -t pg_service -e pg_reload=true
bin/pgsql-svc <cls> [ip...]       # pgsql.yml -l ip... -t pg_service -e pg_reload=true

Reload HBARule

This task is used when your Postgres/Pgbouncer HBA rules have changed, you may have to reload hba to apply changes.

If you have any role-specific HBA rules, you may have to reload hba after a switchover/failover, too.

To reload postgres & pgbouncer HBA rules on entire cluster or specific instances:

bin/pgsql-hba <cls>               # pgsql.yml -l <cls> -t pg_hba,pg_reload,pgbouncer_hba,pgbouncer_reload -e pg_reload=true
bin/pgsql-hba <cls> [ip...]       # pgsql.yml -l ip... -t pg_hba,pg_reload,pgbouncer_hba,pgbouncer_reload -e pg_reload=true

Config Cluster

To change the config of a existing Postgres cluster, you have to initiate control command on admin node with admin user:

pg edit-config <cls>              # interactive config a cluster with patronictl

Change patroni parameters & postgresql.parameters, save & apply changes with the wizard.


Append Replica

To add a new replica to the existing Postgres cluster, you have to add its definition to the inventory: all.children.<cls>.hosts, then:

bin/node-add <ip>                 # init node <ip> for the new replica
bin/pgsql-add <cls> <ip>          # init pgsql instances on <ip> for cluster <cls>

It will add node <ip> to pigsty and init it as a replica of the cluster <cls>.

Cluster services will be reloaded to adopt the new member


Remove Replica

To remove a replica from the existing PostgreSQL cluster:

bin/pgsql-rm <cls> <ip...>        # ./pgsql-rm.yml -l <ip>

It will remove instance <ip> from cluster <cls>. Cluster services will be reloaded to kick the removed instance from load balancer.


Remove Cluster

To remove the entire Postgres cluster, just run:

bin/pgsql-rm <cls>                # ./pgsql-rm.yml -l <cls>

Switchover

You can perform a PostgreSQL cluster switchover with patroni cmd.

pg switchover <cls>   # interactive mode, you can skip that with following options
pg switchover --leader pg-test-1 --candidate=pg-test-2 --scheduled=now --force pg-test

Backup Cluster

To create a backup with pgBackRest, run as local dbsu:

pg-backup                         # make a postgres base backup
pg-backup full                    # make a full backup
pg-backup diff                    # make a differential backup
pg-backup incr                    # make a incremental backup
pb info                           # check backup information

Check Backup & PITR for details.


Restore Cluster

To restore a cluster to a previous time point (PITR), run as local dbsu:

./pgsql-pitr.yml -e '{"pg_pitr": { "time": "2025-07-13 10:00:00+00" }}'
./pgsql-pitr.yml -e '{"pg_pitr": { "name": "shit_incoming" }}'
./pgsql-pitr.yml -e '{"pg_pitr": { "xid": "250000", exclusive: true }}'
./pgsql-pitr.yml -e '{"pg_pitr": { "lsn": "0/4001C80", timeline: "1" }}'

Check Backup & Restore docs for more details.


Adding Packages

To add the newer version of RPM/DEB packages, you have to add them to repo_packages and repo_url_packages

Then rebuild repo on infra nodes with ./infra.yml -t repo_build subtask, Then you can install these packages with ansible module package:

ansible pg-test -b -m package -a "name=pg_cron_15,topn_15,pg_stat_monitor_15*"  # install some packages

Install Extension

If you want to install extension on pg clusters, Add them to pg_extensions and make sure them installed with:

./pgsql.yml -t pg_ext     # install extensions

Some extension needs to be loaded in shared_preload_libraries, You can add them to pg_libs, or Config an existing cluster.

Finally, CREATE EXTENSION <extname>; on the cluster primary instance to install it.

Check PGSQL Extensions: Install for details.


Minor Upgrade

To perform a minor server version upgrade/downgrade, you have to add packages to yum/apt repo first.

Then perform a rolling upgrade/downgrade from all replicas, then switchover the cluster to upgrade the leader.

ansible <cls> -b -a "yum upgrade/downgrade -y <pkg>"    # upgrade/downgrade packages
pg restart --force <cls>                                # restart cluster

Major Upgrade

The simplest way to achieve a major version upgrade is to create a new cluster with the new version, then migration with logical replication & green/blue deployment.

You can also perform an in-place major upgrade, which is not recommended, especially when certain extensions are installed. But it is possible.

Assume you want to upgrade PostgreSQL 14 to 15, you have to add packages to yum/apt repo, and guarantee the extensions have the exact same version too.

./pgsql.yml -t pg_pkg -e pg_version=15                         # install packages for pg 15
sudo su - postgres; mkdir -p /data/postgres/pg-meta-15/data/   # prepare directories for 15
pg_upgrade -b /usr/pgsql-14/bin/ -B /usr/pgsql-15/bin/ -d /data/postgres/pg-meta-14/data/ -D /data/postgres/pg-meta-15/data/ -v -c # preflight
pg_upgrade -b /usr/pgsql-14/bin/ -B /usr/pgsql-15/bin/ -d /data/postgres/pg-meta-14/data/ -D /data/postgres/pg-meta-15/data/ --link -j8 -v -c
rm -rf /usr/pgsql; ln -s /usr/pgsql-15 /usr/pgsql;             # fix binary links
mv /data/postgres/pg-meta-14 /data/postgres/pg-meta-15         # rename data directory
rm -rf /pg; ln -s /data/postgres/pg-meta-15 /pg                # fix data dir links

4.1 - Parameter Tuning

Tuning PostgreSQL parameters

Pigsty provides four scenario-specific parameter templates by default, which can be specified and used via the pg_conf parameter.

  • tiny.yml: Optimized for small nodes, virtual machines, and small demos (1-8 cores, 1-16GB)
  • oltp.yml: Optimized for OLTP workloads and latency-sensitive applications (4C8GB+) (default template)
  • olap.yml: Optimized for OLAP workloads and throughput (4C8G+)
  • crit.yml: Optimized for data consistency and critical applications (4C8G+)

Pigsty adopts different parameter optimization strategies for these four default scenarios, as shown below:


Memory Parameters

Pigsty automatically detects system memory size and uses it as the basis for setting maximum connections and memory-related parameters.

  • pg_max_conn: PostgreSQL maximum connections, auto will use recommended values for different scenarios
  • pg_shared_buffer_ratio: Shared buffer memory ratio, defaults to 0.25

By default, Pigsty uses 25% of memory as PostgreSQL shared buffers, leaving the remaining 75% for the operating system cache.

By default, if users don’t set a pg_max_conn maximum connection count, Pigsty will use default values according to these rules:

  • oltp: 500 (pgbouncer) / 1000 (postgres)
  • crit: 500 (pgbouncer) / 1000 (postgres)
  • tiny: 300
  • olap: 300

For OLTP and CRIT templates, if services point directly to the PostgreSQL database instead of the pgbouncer connection pool, maximum connections double to 1000.

After determining maximum connections, work_mem is calculated based on shared memory amount / maximum connections, constrained within a 64MB ~ 1GB range.

{% if pg_max_conn != 'auto' and pg_max_conn|int >= 20 %}{% set pg_max_connections = pg_max_conn|int %}{% else %}{% if pg_default_service_dest|default('postgres') == 'pgbouncer' %}{% set pg_max_connections = 500 %}{% else %}{% set pg_max_connections = 1000 %}{% endif %}{% endif %}
{% set pg_max_prepared_transactions = pg_max_connections if 'citus' in pg_libs else 0 %}
{% set pg_max_locks_per_transaction = (2 * pg_max_connections)|int if 'citus' in pg_libs or 'timescaledb' in pg_libs else pg_max_connections %}
{% set pg_shared_buffers = (node_mem_mb|int * pg_shared_buffer_ratio|float) | round(0, 'ceil') | int %}
{% set pg_maintenance_mem = (pg_shared_buffers|int * 0.25)|round(0, 'ceil')|int %}
{% set pg_effective_cache_size = node_mem_mb|int - pg_shared_buffers|int  %}
{% set pg_workmem =  ([ ([ (pg_shared_buffers / pg_max_connections)|round(0,'floor')|int , 64 ])|max|int , 1024])|min|int %}

CPU Parameters

In PostgreSQL, there are 4 important parameters related to parallel queries. Pigsty automatically optimizes these parameters based on the current system’s CPU core count. Across all strategies, the total parallel process count (total budget) is typically set to CPU cores + 8, with a minimum of 16, thus reserving sufficient background worker capacity for logical replication and extensions. OLAP and TINY templates vary slightly based on scenario.

OLTP Configuration Logic Range Constraints
max_worker_processes max(100% CPU + 8, 16) Cores + 4, minimum 12
max_parallel_workers max(ceil(50% CPU), 2) 1/2 CPU rounded up, minimum 2
max_parallel_maintenance_workers max(ceil(33% CPU), 2) 1/3 CPU rounded up, minimum 2
max_parallel_workers_per_gather min(max(ceil(20% CPU), 2),8) 1/5 CPU rounded down, minimum 2, maximum 8
OLAP Configuration Logic Range Constraints
max_worker_processes max(100% CPU + 12, 20) Cores + 12, minimum 20
max_parallel_workers max(ceil(80% CPU, 2)) 4/5 CPU rounded up, minimum 2
max_parallel_maintenance_workers max(ceil(33% CPU), 2) 1/3 CPU rounded up, minimum 2
max_parallel_workers_per_gather max(floor(50% CPU), 2) 1/2 CPU rounded up, minimum 2
CRIT Configuration Logic Range Constraints
max_worker_processes max(100% CPU + 8, 16) Cores + 8, minimum 16
max_parallel_workers max(ceil(50% CPU), 2) 1/2 CPU rounded up, minimum 2
max_parallel_maintenance_workers max(ceil(33% CPU), 2) 1/3 CPU rounded up, minimum 2
max_parallel_workers_per_gather 0, enable as needed
TINY Configuration Logic Range Constraints
max_worker_processes max(100% CPU + 4, 12) Cores + 4, minimum 12
max_parallel_workers max(ceil(50% CPU) 1) 50% CPU rounded down, minimum 1
max_parallel_maintenance_workers max(ceil(33% CPU), 1) 33% CPU rounded down, minimum 1
max_parallel_workers_per_gather 0, enable as needed

Note that CRIT and TINY templates disable parallel queries directly by setting max_parallel_workers_per_gather = 0. Users can set this parameter as needed to enable parallel queries.

Both OLTP and CRIT templates set the following additional parameters, doubling parallel query costs to reduce the tendency to use parallel queries:

parallel_setup_cost: 2000           # double from 100 to increase parallel cost
parallel_tuple_cost: 0.2            # double from 0.1 to increase parallel cost
min_parallel_table_scan_size: 16MB  # double from 8MB to increase parallel cost
min_parallel_index_scan_size: 1024  # double from 512 to increase parallel cost

Note that max_worker_processes parameter adjustments only take effect after a restart. Additionally, when a replica’s configuration value for this parameter exceeds the primary’s, the replica cannot start. This parameter must be adjusted through Patroni configuration management. The parameter is managed by Patroni to ensure consistent primary-replica configuration and prevent new replicas from failing to start during failover.


Storage Parameters

Pigsty automatically detects the total disk space where the /data/postgres main data directory resides and uses it as the basis for specifying the following parameters:

min_wal_size: {{ ([pg_size_twentieth, 200])|min }}GB                  # 1/20 disk size, max 200GB
max_wal_size: {{ ([pg_size_twentieth * 4, 2000])|min }}GB             # 2/10 disk size, max 2000GB
max_slot_wal_keep_size: {{ ([pg_size_twentieth * 6, 3000])|min }}GB   # 3/10 disk size, max 3000GB
temp_file_limit: {{ ([pg_size_twentieth, 200])|min }}GB               # 1/20 of disk size, max 200GB
  • temp_file_limit defaults to 5% of disk space, capped at 200GB maximum.
  • min_wal_size defaults to 5% of disk space, capped at 200GB maximum.
  • max_wal_size defaults to 20% of disk space, capped at 2TB maximum.
  • max_slot_wal_keep_size defaults to 30% of disk space, capped at 3TB maximum.

As a special case, the OLAP template allows 20% for temp_file_limit, capped at 2TB maximum.

4.2 - Maintenance

Common system maintenance tasks

Ensuring healthy and stable operation of Pigsty and PostgreSQL clusters requires routine maintenance work.


Regular Monitoring Review

Pigsty provides an out-of-the-box monitoring platform. We recommend reviewing monitoring dashboards daily to track system status. At minimum, we suggest weekly monitoring reviews, focusing on alert events to proactively avoid most failures and issues.

Here’s a list of predefined alert rules in Pigsty.


Failover Follow-up

Pigsty’s high availability architecture allows PostgreSQL clusters to automatically perform primary-replica switching, meaning operations and DBAs don’t require immediate intervention. However, users still need to perform follow-up tasks at appropriate times (e.g., next business day), including:

  • Investigate and confirm failure root cause to prevent recurrence
  • Optionally restore original primary-replica topology or update configuration manifest to match new state
  • Refresh load balancer configuration via bin/pgsql-svc to update service routing state
  • Refresh cluster HBA rules via bin/pgsql-hba to prevent primary-replica specific rule drift
  • If necessary, remove failed servers with bin/pgsql-rm and expand with new replicas using bin/pgsql-add

Bloat Control

Long-running PostgreSQL instances develop “table bloat” / “index bloat”, degrading system performance.

Regular online rebuilding of tables and indexes using pg_repack helps maintain optimal PostgreSQL performance. Pigsty installs and enables this extension by default in all databases, ready for immediate use.

You can check table and index bloat through Pigsty’s PGCAT Database - Table Bloat panel. Select tables and indexes with high bloat rates (larger tables with >50% bloat) for online reorganization using pg_repack:

pg_repack dbname -t schema.table

Normal reads/writes continue during reorganization, but the switch moment at completion requires an AccessExclusive lock, blocking all access. For high-throughput operations, schedule during low-traffic periods or maintenance windows. For more details, see: Managing Relation Bloat


VACUUM FREEZE

Freezing expired transaction IDs (VACUUM FREEZE) is a critical PostgreSQL maintenance task preventing transaction ID (XID) exhaustion outages. While PostgreSQL provides AutoVacuum mechanisms, for high-standard production environments, we recommend combining automatic and manual approaches, regularly executing database-wide VACUUM FREEZE to ensure XID safety.

4.3 - Failure SOP

Common failures and troubleshooting strategies

This document outlines potential failures in PostgreSQL and Pigsty, along with SOPs for diagnosing, handling, and analyzing issues.


Disk Space Exhaustion

Disk space exhaustion is the most common type of failure.

Symptoms

When the disk hosting the database runs out of space, PostgreSQL cannot function properly. You may observe: database logs repeatedly reporting “no space left on device”, inability to write new data, or PostgreSQL triggering a PANIC and forcing shutdown.

Pigsty includes a NodeFsSpaceFull alert rule that triggers when filesystem available space drops below 10%. Use the monitoring system’s NODE Instance panel to review FS metric panels for diagnosis.

Diagnosis

You can also log into the database node and use df -h to check usage rates for each mount point, determining which partition is full. For database nodes, focus on these directories and their sizes to determine which file category is consuming space:

  • Data directory (/pg/data/base): Stores table and index data files, watch for heavy writes and temporary files
  • WAL directory (e.g., pg/data/pg_wal): Stores PG WAL, WAL accumulation/replication slot retention are common causes of disk exhaustion
  • Database log directory (e.g., pg/log): If PG logs aren’t rotated timely and massive errors are written, this can consume significant space
  • Local backup directory (e.g., data/backups): When using pgBackRest to save backups locally, this can also fill the disk

For Pigsty admin nodes or monitoring nodes, also consider:

  • Monitoring data: Both Prometheus time-series metrics storage and Loki log storage consume disk space, check retention policies
  • Object storage data: Pigsty’s integrated MinIO object storage may be used for PG backup storage

After identifying directories consuming the most space, use du -sh <directory> to drill down for specific large files or subdirectories.

Resolution

Disk exhaustion is an emergency requiring immediate action to free space and maintain database operation:

Emergency scenario: When data and system disks aren’t separated, disk exhaustion can prevent shell commands from executing. In this case, delete the /pg/dummy placeholder file to free emergency space for shell command recovery.

After freeing space with above measures, PostgreSQL should resume normal operation. If the database crashed due to pg_wal exhaustion, restart the database service after clearing space and carefully verify data integrity.


Transaction ID Wraparound

PostgreSQL uses 32-bit transaction IDs (XIDs) cyclically. When XIDs are exhausted, “transaction ID wraparound” failure occurs.

Symptoms

Initial symptoms include PGSQL Persist - Age Usage panel age saturation entering the warning zone. Database logs begin showing: WARNING: database "postgres" must be vacuumed within xxxxxxxx transactions.

If the problem worsens, PostgreSQL enters protection mode: when remaining transaction IDs drop below ~1 million, the database switches to read-only mode; at the limit of ~2.1 billion (2^31), it refuses new transactions and forces server shutdown to prevent data corruption.

Diagnosis

PostgreSQL and Pigsty enable AutoVacuum by default, so this failure usually indicates deeper root causes. Common causes include: super-aged transactions (SAGE), misconfigured Autovacuum, blocked replication slots, insufficient resources, storage engine/extension bugs, disk corruption.

First identify the database with the oldest age, then use the Pigsty PGCAT Database - Tables panel to check table age distribution. Review database error logs for clues to identify root causes.

Resolution

  1. Immediate transaction freezing: If the database hasn’t entered read-only protection, immediately execute manual VACUUM FREEZE on affected databases. Start with the most aged tables rather than the entire database to expedite results. As superuser, run VACUUM FREEZE tablename; on tables with highest relfrozenxid, prioritizing tables with oldest XID age. This quickly reclaims significant transaction ID space.
  2. Single-user mode rescue: If the database refuses writes or has crashed for protection, start the database in single-user mode for freeze operations. In single-user mode, run VACUUM FREEZE database_name; to freeze-clean the entire database. Then restart in multi-user mode. This releases wraparound locks and restores write capability. Exercise extreme caution in single-user mode and ensure sufficient transaction ID headroom for freezing.
  3. Standby takeover: In complex scenarios (e.g., hardware issues preventing vacuum completion), consider promoting a read-only standby to primary for a cleaner environment. For example, if the primary has bad blocks preventing vacuum, manually failover to promote the standby as new primary, then perform emergency vacuum freeze. After ensuring the new primary has frozen old transactions, switch load back.

Connection Exhaustion

PostgreSQL has a maximum connection limit (max_connections). When client connections exceed this limit, new connection requests are rejected. Typical symptoms include applications unable to connect with errors like FATAL: remaining connection slots are reserved for non-replication superuser connections or too many clients already. This indicates regular connection slots are exhausted, leaving only slots reserved for superusers or replication.

Diagnosis

Connection exhaustion typically results from massive concurrent client requests. You can review current active sessions through PGCAT Instance / PGCAT Database / PGCAT Locks to determine what queries are filling the system for further action. Pay special attention to numerous Idle in Transaction connections and long-running transactions (and slow queries).

Resolution

Kill queries: For exhaustion blocking business operations, immediately use pg_terminate_backend(pid) for emergency relief. For connection pool users, adjust pool size parameters and reload to reduce database-level connections.

You can also use pg edit-config to increase max_connections, but this parameter requires database restart to take effect.


etcd Quota Exhaustion

etcd quota exhaustion causes PG high availability control plane failure, preventing configuration changes. Versions between Pigsty v2.0.0 - v2.5.1 are affected by default.

Diagnosis

Pigsty uses etcd as distributed configuration storage (DCS) for high availability. etcd has a storage quota (default ~2GB). When etcd storage reaches the quota limit, etcd refuses write operations with error “etcdserver: mvcc: database space exceeded”. In this state, Patroni cannot write heartbeats or update configurations to etcd, causing cluster management failure.

Resolution

Pigsty v2.6.0 adds auto-compaction configuration for deployed etcd. If you only use it for PG high availability leases, regular use cases won’t encounter this issue.


Defective Storage Engines

Currently, TimescaleDB’s experimental Hypercore storage engine has proven defects, with documented cases of VACUUM failing to reclaim XIDs causing wraparound failures. Users of this feature should migrate promptly to PostgreSQL native tables or TimescaleDB’s default engine.

Details: PG New Storage Engine Failure Case

4.4 - Data Loss Recovery

Handling accidental deletion of data, tables, and databases

Accidental Data Deletion

For small-batch DELETE operations performed in error, consider using the pg_surgery extension for in-place surgical recovery.

If the deleted data has already been reclaimed by VACUUM, follow the general data loss recovery workflow.

Accidental Object Deletion

When DROP/DELETE operations are performed in error, follow this workflow to determine the recovery approach:

  1. Verify if the data can be recovered through business systems or other data sources. If possible, recover directly from the business side.
  2. Check for delayed replica availability. If available, advance the delayed replica to the point before deletion and query the data for recovery.
  3. If data is confirmed deleted, verify backup coverage for the deletion timepoint. If covered, initiate PITR.
  4. Determine whether to perform in-place PITR rollback on the entire cluster, replay on a new server, or use a replica for replay, then execute the recovery strategy.

Accidental Cluster Deletion

In cases where an entire database cluster is accidentally deleted, such as mistakenly executing the pgsql-rm.yml playbook: Unless you explicitly specified pg_rm_backup: false beforehand, backups will typically be deleted along with the database cluster.

5 - Playbook

control primitives

How to manage PostgreSQL cluster with ansible playbooks

Pigsty has a series of playbooks for PostgreSQL:

  • pgsql.yml : Init HA PostgreSQL clusters or add new replicas.
  • pgsql-rm.yml : Remove PostgreSQL cluster, or remove replicas
  • pgsql-db.yml : Add a new business database to existing PostgreSQL cluster
  • pgsql-user.yml : Add new business user to existing PostgreSQL cluster
  • pgsql-pitr.yml : Perform point-in-time recovery on existing PostgreSQL cluster
  • pgsql-monitor.yml : Monitor remote PostgreSQL instance with local exporters
  • pgsql-migration.yml : Generate Migration manual & scripts for existing PostgreSQL

Safeguard

If you are afraid of accidentally deleting your PostgreSQL cluster, you can enable the safeguard feature.

Setting the pg_safeguard parameter to true will stop the pgsql-rm.yml from running.

Before Pigsty v3.5, pgsql.yml can nuke your database with fat finger, use with caution!

Pigsty v3.5 remove the pg purge logic from pgsql.yml, So the only way to remove a PostgreSQL now is running pgsql-rm.yml.


pgsql.yml

The pgsql.yml is used for init HA PostgreSQL clusters or adding new replicas.

asciicast

This playbook contains the following subtasks:

pg_install              : # install postgres packages & extensions
  - pg_dbsu             : # setup os user sudo for postgres dbsu
    - pg_dbsu_create    : # exchange dbsu ssh keys
    - pg_dbsu_sudo      : # exchange dbsu ssh keys
    - pg_ssh            : # exchange dbsu ssh keys
  - pg_pkg              : # install postgres packages
    - pg_ext            : # install postgres extension packages
  - pg_link             : # link pgsql version bin to /usr/pgsql
  - pg_path             : # add pgsql bin to system path
  - pg_dir              : # create postgres directories and setup fhs
  - pg_bin              : # sync /pg/bin scripts
  - pg_alias            : # write pgsql/psql alias
  - pg_dummy            : # create dummy placeholder file
pg_bootstrap            : # bootstrap postgres cluster
  - pg_config           : # generate postgres config
    - pg_conf           : # generate patroni config
    - pg_key            : # generate pgsodium key
    - pg_pitr_conf      : # generate optional pitr config
  - pg_cert             : # issues certificates for postgres
    - pg_cert_private   : # check pg private key existence
    - pg_cert_issue     : # signing pg server certs
    - pg_cert_copy      : # copy key & certs to pg node
  - pg_launch           : # launch patroni primary & replicas  (patroni)
    - pg_watchdog       : # grant watchdog permission to postgres
    - pg_primary        : # launch patroni/postgres primary
    - pg_init           : # init pg cluster with roles/templates
    - pg_pass           : # write .pgpass file to pg home
    - pg_replica        : # launch patroni/postgres replicas
    - pg_hba            : # generate pg HBA rules
    - patroni_reload    : # reload patroni config
    - pg_patroni        : # pause or remove patroni if necessary
pg_provision            : # provision postgres business users & databases
 - pg_user              : # provision postgres business users
    - pg_user_config    : # render create user sql
    - pg_user_create    : # create user on postgres
 - pg_db                : # provision postgres business databases
    - pg_db_config      : # render create database sql
    - pg_db_create      : # create database on postgres
pg_backup               : # init postgres PITR backup
  - pgbackrest          : # setup pgbackrest for backup
    - pgbackrest_config : # generate pgbackrest config
    - pgbackrest_init   : # init pgbackrest repo
    - pgbackrest_backup : # make a initial backup after bootstrap
pg_access               : # init postgres service access, pool, dns, vip, svc
 - pgbouncer            : # deploy a pgbouncer sidecar with postgres
   - pgbouncer_dir      : # create pgbouncer directories
   - pgbouncer_config   : # generate pgbouncer config
     -  pgbouncer_hba   : # generate pgbouncer hba config
     -  pgbouncer_user  : # generate pgbouncer userlist
   -  pgbouncer_launch  : # launch pgbouncer pooling service
   -  pgbouncer_reload  : # reload pgbouncer config
 - pg_vip               : # bind vip to pgsql primary with vip-manager
   - pg_vip_config      : # generate config for vip-manager
   - pg_vip_launch      : # launch vip-manager to bind vip
 - pg_dns               : # register dns name to infra dnsmasq
   - pg_dns_ins         : # register pg instance name
   - pg_dns_cls         : # register pg cluster name
 - pg_service           : # expose pgsql service with haproxy
   - pg_service_config  : # generate local haproxy config for pg services
   - pg_service_reload  : # expose postgres services with haproxy
pg_monitor              : # setup pgsql monitor and register to infra
  - pg_exporter         : # config & launch pg_exporter
  - pgbouncer_exporter  : # config & launch pgbouncer_exporter
  - pgbackrest_exporter : # config & launch pgbackrest_exporter
  - register_prometheus : # register pg as prometheus monitor targets
  - register_grafana    : # register pg database as grafana datasource

Administration Tasks that use this playbook

Init Primary before Replicas
  • you may have to run Reload HBARule and Append Replica after replica init.
  • The wrap script pgsql-add will do this, check SOP: Add Instance for details.
  • If you run this on the entire cluster, you don’t have to worry about this.
Init Upstream cluster before Standby Clusters
  • If you are initializing a standby cluster, you should make sure the upstream cluster is already initialized.

pgsql-rm.yml

The playbook pgsql-rm.yml can remove PostgreSQL cluster, or specific replicas from cluster.

asciicast

This playbook contains the following subtasks:

pg_monitor               : # remove registration in prometheus, grafana, nginx
  - prometheus           : # remove monitor target from prometheus
  - grafana              : # remove datasource from grafana
  - pg_exporter          : # remove pg_exporter (postgres monitoring)
  - pgbouncer_exporter   : # remove pgbouncer_exporter (pgbouncer monitoring)
  - pgbackrest_exporter  : # remove pgbackrest_exporter (pgbackrest monitoring)
pg_access                : # remove pg service access
  - dns                  : # remove pg dns records
  - vip                  : # remove vip-manager
  - pg_service           : # remove pg service from haproxy
  - pgbouncer            : # remove pgbouncer connection middleware
postgres                 : # remove postgres instances
  - pg_replica           : # remove all replicas
  - pg_primary           : # remove primary instance
  - pg_meta              : # remove metadata from dcs
pg_backup                : # remove backup repo    (disable with `pg_rm_backup=false`)
pg_data                  : # remove postgres data  (disable with `pg_rm_data=false`)
pg_pkg                   : # uninstall pg packages (disable with `pg_rm_pkg=false`)
 - pg_ext                : # uninstall postgres extensions alone

Some arguments can affect the behavior of this playbook:

# remove pgsql cluster `pg-test`
./pgsql-rm.yml                          # remove all the postgres clusters (VERY DANGEROUS)
./pgsql-rm.yml -l pg-test               # remove the cluster `pg-test`
./pgsql-rm.yml -e pg_safeguard=false    # force disable safeguard, run this playbook anyway
./pgsql-rm.yml -e pg_rm_data=false        # keep the data directory, do not remove it (keep the data)
./pgsql-rm.yml -e pg_rm_backup=false      # do not purge postgres data by default (keep the backup repo)
./pgsql-rm.yml -e pg_rm_pkg=false     # do not uninstall postgres packages by default (keep the packages)

Administration Tasks that use this playbook

Some notes about this playbook

Do not run this playbook on single cluster primary directly when there are still replicas
  • otherwise, the rest replicas will trigger automatic failover.
  • It won’t be a problem if you remove all replicas before removing primary.
  • If you run this on the entire cluster, you don’t have to worry about this.
Reload service after removing replicas from cluster
  • It is a dead server, so it won’t affect the cluster service.
  • But you should reload service in time to ensure the consistency between the environment and the config inventory.
  • When a replica is removed, it is still in the configuration file of the haproxy load balancer.

pgsql-db.yml

The playbook pgsql-db.yml can add new business database to existing PostgreSQL cluster.

Check admin SOP: Create Database


pgsql-user.yml

The playbook pgsql-user.yml can add new business user to existing PostgreSQL cluster.

Check admin SOP: Create User


pgsql-pitr.yml

The playbook pgsql-pitr.yml can perform point-in-time recovery on existing PostgreSQL cluster.

Check admin SOP: Restore


pgsql-monitor.yml

The playbook pgsql-monitor.yml can monitor remote postgres instance with local exporters.

Check admin SOP: Monitor Postgres


pgsql-migration.yml

The playbook pgsql-migration.yml can generate migration manual & scripts for existing PostgreSQL cluster.

Check admin SOP: Migration

6 - Monitor

Monitor existing PostgreSQL or RDS

Overview

Pigsty uses the modern observability stack for PostgreSQL monitoring:

  • Grafana for metrics visualization and PostgreSQL datasource.
  • Prometheus for PostgreSQL / Pgbouncer / Patroni / HAProxy / Node metrics
  • Loki for PostgreSQL / Pgbouncer / Patroni / pgBackRest logs
  • Battery-Include dashboards for PostgreSQL and everything else

Metrics

PostgreSQL’s metrics are defined by collector files: pg_exporter.yml. Prometheus record rules and alert evaluation will further process it: files/prometheus/rules/pgsql.yml

There are three identity labels: cls, ins, ip, which will be attached to all metrics & logs. node & haproxy will try to reuse the same identity to provide consistent metrics & logs.

{ cls: pg-meta, ins: pg-meta-1, ip: 10.10.10.10 }
{ cls: pg-meta, ins: pg-test-1, ip: 10.10.10.11 }
{ cls: pg-meta, ins: pg-test-2, ip: 10.10.10.12 }
{ cls: pg-meta, ins: pg-test-3, ip: 10.10.10.13 }

Logs

PostgreSQL-related logs are collected by promtail and sent to Loki on infra nodes by default.

Targets

Prometheus monitoring targets are defined in static files under /etc/prometheus/targets/pgsql/. Each instance will have a corresponding file. Take pg-meta-1 as an example:

# pg-meta-1 [primary] @ 10.10.10.10
- labels: { cls: pg-meta, ins: pg-meta-1, ip: 10.10.10.10 }
  targets:
    - 10.10.10.10:9630    # <--- pg_exporter for PostgreSQL metrics
    - 10.10.10.10:9631    # <--- pg_exporter for Pgbouncer metrics
    - 10.10.10.10:8008    # <--- patroni metrics

When the global flag patroni_ssl_enabled is set, the patroni target will be managed as /etc/prometheus/targets/patroni/<ins>.yml because it requires a different scrape endpoint (https).

Prometheus monitoring target will be removed when a cluster is removed by bin/pgsql-rm or pgsql-rm.yml. You can use playbook subtasks, or remove them manually:

bin/pgmon-rm <ins>      # remove prometheus targets from all infra nodes

Remote RDS targets are managed as /etc/prometheus/targets/pgrds/<cls>.yml. It will be created by the pgsql-monitor.yml playbook or bin/pgmon-add script.


Monitor Mode

There are three ways to monitor PostgreSQL instances in Pigsty:

Item \ Level L1 L2 L3
Name Remote Database Service Existing Deployment Fully Managed Deployment
Abbr RDS MANAGED FULL
Scenes connect string URL only ssh-sudo-able Instances created by Pigsty
PGCAT Functionality ✅ Full Availability ✅ Full Availability ✅ Full Availability
PGSQL Functionality ✅ PG metrics only ✅ PG and node metrics ✅ Full Support
Connection Pool Metrics ❌ Not available ⚠️ Optional ✅ Pre-Configured
Load Balancer Metrics ❌ Not available ⚠️ Optional ✅ Pre-Configured
PGLOG Functionality ❌ Not Available ⚠️ Optional ⚠️ Optional
PG Exporter ⚠️ On infra nodes ✅ On DB nodes ✅ On DB nodes
Node Exporter ❌ Not Deployed ✅ On DB nodes ✅ On DB nodes
Intrusion into DB nodes ✅ Non-Intrusive ⚠️ Installing Exporter ⚠️ Fully Managed by Pigsty
Instance Already Exists ✅ Yes ✅ Yes ⚠️ Created by Pigsty
Monitoring users and views ⚠️Manually Setup ⚠️Manually Setup ✅ Auto configured
Deployment Usage Playbook bin/pgmon-add <cls> subtasks of pgsql.ym/node.yml pgsql.yml
Required Privileges connectable PGURL from infra nodes DB node ssh and sudo privileges DB node ssh and sudo privileges
Function Overview PGCAT + PGRDS Most Functionality Full Functionality

Monitor Existing Cluster

Suppose the target DB node can be managed by Pigsty (accessible via ssh and sudo is available). In that case, you can use the pg_exporter task in the pgsql.yml playbook to deploy the monitoring component PG Exporter on the target node in the same manner as a standard deployment.

You can also deploy the connection pool and its monitoring on existing instance nodes using the pgbouncer and pgbouncer_exporter tasks from the same playbook. Additionally, you can deploy host monitoring, load balancing, and log collection components using the node_exporter, haproxy, and promtail tasks from the node.yml playbook, achieving a similar user experience with the native Pigsty cluster.

The definition method for existing clusters is very similar to the normal clusters managed by Pigsty. Selectively run certain tasks from the pgsql.yml playbook instead of running the entire playbook.

./node.yml  -l <cls> -t node_repo,node_pkg           # Add YUM sources for INFRA nodes on host nodes and install packages.
./node.yml  -l <cls> -t node_exporter,node_register  # Configure host monitoring and add to Prometheus.
./node.yml  -l <cls> -t promtail                     # Configure host log collection and send to Loki.
./pgsql.yml -l <cls> -t pg_exporter,pg_register      # Configure PostgreSQL monitoring and register with Prometheus/Grafana.

Since the target database cluster already exists, you must manually setup monitoring users, schemas, and extensions on the target database cluster.


Monitor RDS

If you can only access the target database via PGURL (database connection string), you can refer to the instructions here for configuration. In this mode, Pigsty deploys the corresponding PG Exporter on the INFRA node to fetch metrics from the remote database, as shown below:

------ infra ------
|                 |
|   prometheus    |            v---- pg-foo-1 ----v
|       ^         |  metrics   |         ^        |
|   pg_exporter <-|------------|----  postgres    |
|   (port: 20001) |            | 10.10.10.10:5432 |
|       ^         |            ^------------------^
|       ^         |                      ^
|       ^         |            v---- pg-foo-2 ----v
|       ^         |  metrics   |         ^        |
|   pg_exporter <-|------------|----  postgres    |
|   (port: 20002) |            | 10.10.10.11:5433 |
-------------------            ^------------------^

The monitoring system will no longer have host/pooler/load balancer metrics. But the PostgreSQL metrics & catalog info are still available. Pigsty has two dedicated dashboards for that: PGRDS Cluster and PGRDS Instance. Overview and Database level dashboards are reused. Since Pigsty cannot manage your RDS, you have to setup monitor on the target database in advance.

Below, we use a sandbox environment as an example: now we assume that the pg-meta cluster is an RDS instance pg-foo-1 to be monitored, and the pg-test cluster is an RDS cluster pg-bar to be monitored:

  1. Create monitoring schemas, users, and permissions on the target. Refer to Monitor Setup for details.

  2. Declare the cluster in the configuration list. For example, suppose we want to monitor the “remote” pg-meta & pg-test clusters:

infra:            # Infra cluster for proxies, monitoring, alerts, etc.
  hosts: { 10.10.10.10: { infra_seq: 1 } }
  vars:           # Install pg_exporter on 'infra' group for remote postgres RDS
    pg_exporters: # List all remote instances here, assign a unique unused local port for k
      20001: { pg_cluster: pg-foo, pg_seq: 1, pg_host: 10.10.10.10 , pg_databases: [{ name: meta }] } # Register meta database as Grafana data source

      20002: { pg_cluster: pg-bar, pg_seq: 1, pg_host: 10.10.10.11 , pg_port: 5432 } # Several different connection string concatenation methods
      20003: { pg_cluster: pg-bar, pg_seq: 2, pg_host: 10.10.10.12 , pg_exporter_url: 'postgres://dbuser_monitor:[email protected]:5432/postgres?sslmode=disable'}
      20004: { pg_cluster: pg-bar, pg_seq: 3, pg_host: 10.10.10.13 , pg_monitor_username: dbuser_monitor, pg_monitor_password: DBUser.Monitor }

The databases listed in the pg_databases field will be registered in Grafana as a PostgreSQL data source, providing data support for the PGCAT monitoring panel. If you don’t want to use PGCAT and register the database in Grafana, set pg_databases to an empty array or leave it blank.

pigsty-monitor.jpg
  1. Execute the command to add monitoring: bin/pgmon-add <clsname>
bin/pgmon-add pg-foo  # Bring the pg-foo cluster into monitoring
bin/pgmon-add pg-bar  # Bring the pg-bar cluster into monitoring
  1. To remove a remote cluster from monitoring, use bin/pgmon-rm <clsname>
bin/pgmon-rm pg-foo  # Remove pg-foo from Pigsty monitoring
bin/pgmon-rm pg-bar  # Remove pg-bar from Pigsty monitoring

You can use more parameters to override the default pg_exporter options. Here is an example for monitoring Aliyun RDS and PolarDB with Pigsty:


Monitor Setup

When you want to monitor existing instances, whether it’s RDS or a self-built PostgreSQL instance, you need to make some configurations on the target database so that Pigsty can access them.

To bring an external existing PostgreSQL instance into monitoring, you need a connection string that can access that instance/cluster. Any accessible connection string (business user, superuser) can be used, but we recommend using a dedicated monitoring user to avoid permission leaks.

  • Monitor User: The default username used is dbuser_monitor. This user belongs to the pg_monitor group, or ensure it has the necessary view permissions.
  • Monitor HBA: Default password is DBUser.Monitor. You need to ensure that the HBA policy allows the monitoring user to access the database from the infra nodes.
  • Monitor Schema: It’s optional but recommended to create a dedicate schema monitor for monitoring views and extensions.
  • Monitor Extension: It is strongly recommended to enable the built-in extension pg_stat_statements.
  • Monitor View: Monitoring views are optional but can provide additional metrics. Which is recommended.

Monitor User

Create a monitor user on the target database cluster. For example, dbuser_monitor is used by default in Pigsty.

CREATE USER dbuser_monitor;                                       -- create the monitor user
COMMENT ON ROLE dbuser_monitor IS 'system monitor user';          -- comment the monitor user
GRANT pg_monitor TO dbuser_monitor;                               -- grant system role pg_monitor to monitor user

ALTER USER dbuser_monitor PASSWORD 'DBUser.Monitor';              -- set password for monitor user
ALTER USER dbuser_monitor SET log_min_duration_statement = 1000;  -- set this to avoid log flooding
ALTER USER dbuser_monitor SET search_path = monitor,public;       -- set this to avoid pg_stat_statements extension not working

The monitor user here should have consistent pg_monitor_username and pg_monitor_password with Pigsty config inventory.


Monitor HBA

You also need to configure pg_hba.conf to allow monitoring user access from infra/admin nodes.

# allow local role monitor with password
local   all  dbuser_monitor                    md5
host    all  dbuser_monitor  127.0.0.1/32      md5
host    all  dbuser_monitor  <admin_ip>/32     md5
host    all  dbuser_monitor  <infra_ip>/32     md5

If your RDS does not support the RAW HBA format, add admin/infra node IP to the whitelist.


Monitor Schema

Monitor schema is optional, but we strongly recommend creating one.

CREATE SCHEMA IF NOT EXISTS monitor;               -- create dedicate monitor schema
GRANT USAGE ON SCHEMA monitor TO dbuser_monitor;   -- allow monitor user to use this schema

Monitor Extension

Monitor extension is optional, but we strongly recommend enabling pg_stat_statements extension.

Note that this extension must be listed in shared_preload_libraries to take effect, and changing this parameter requires a database restart.

CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" WITH SCHEMA "monitor";

You should create this extension inside the admin database: postgres. If your RDS does not grant CREATE on the database postgres. You can create that extension in the default public schema:

CREATE EXTENSION IF NOT EXISTS "pg_stat_statements";
ALTER USER dbuser_monitor SET search_path = monitor,public;

As long as your monitor user can access pg_stat_statements view without schema qualification, it should be fine.


Monitor View

It’s recommended to create the monitor views in all databases that need to be monitored.

Monitor Schema & View Definition

----------------------------------------------------------------------
-- Table bloat estimate : monitor.pg_table_bloat
----------------------------------------------------------------------
DROP VIEW IF EXISTS monitor.pg_table_bloat CASCADE;
CREATE OR REPLACE VIEW monitor.pg_table_bloat AS
SELECT CURRENT_CATALOG AS datname, nspname, relname , tblid , bs * tblpages AS size,
       CASE WHEN tblpages - est_tblpages_ff > 0 THEN (tblpages - est_tblpages_ff)/tblpages::FLOAT ELSE 0 END AS ratio
FROM (
         SELECT ceil( reltuples / ( (bs-page_hdr)*fillfactor/(tpl_size*100) ) ) + ceil( toasttuples / 4 ) AS est_tblpages_ff,
                tblpages, fillfactor, bs, tblid, nspname, relname, is_na
         FROM (
                  SELECT
                      ( 4 + tpl_hdr_size + tpl_data_size + (2 * ma)
                          - CASE WHEN tpl_hdr_size % ma = 0 THEN ma ELSE tpl_hdr_size % ma END
                          - CASE WHEN ceil(tpl_data_size)::INT % ma = 0 THEN ma ELSE ceil(tpl_data_size)::INT % ma END
                          ) AS tpl_size, (heappages + toastpages) AS tblpages, heappages,
                      toastpages, reltuples, toasttuples, bs, page_hdr, tblid, nspname, relname, fillfactor, is_na
                  FROM (
                           SELECT
                               tbl.oid AS tblid, ns.nspname , tbl.relname, tbl.reltuples,
                               tbl.relpages AS heappages, coalesce(toast.relpages, 0) AS toastpages,
                               coalesce(toast.reltuples, 0) AS toasttuples,
                               coalesce(substring(array_to_string(tbl.reloptions, ' ') FROM 'fillfactor=([0-9]+)')::smallint, 100) AS fillfactor,
                               current_setting('block_size')::numeric AS bs,
                               CASE WHEN version()~'mingw32' OR version()~'64-bit|x86_64|ppc64|ia64|amd64' THEN 8 ELSE 4 END AS ma,
                               24 AS page_hdr,
                               23 + CASE WHEN MAX(coalesce(s.null_frac,0)) > 0 THEN ( 7 + count(s.attname) ) / 8 ELSE 0::int END
                                   + CASE WHEN bool_or(att.attname = 'oid' and att.attnum < 0) THEN 4 ELSE 0 END AS tpl_hdr_size,
                               sum( (1-coalesce(s.null_frac, 0)) * coalesce(s.avg_width, 0) ) AS tpl_data_size,
                               bool_or(att.atttypid = 'pg_catalog.name'::regtype)
                                   OR sum(CASE WHEN att.attnum > 0 THEN 1 ELSE 0 END) <> count(s.attname) AS is_na
                           FROM pg_attribute AS att
                                    JOIN pg_class AS tbl ON att.attrelid = tbl.oid
                                    JOIN pg_namespace AS ns ON ns.oid = tbl.relnamespace
                                    LEFT JOIN pg_stats AS s ON s.schemaname=ns.nspname AND s.tablename = tbl.relname AND s.inherited=false AND s.attname=att.attname
                                    LEFT JOIN pg_class AS toast ON tbl.reltoastrelid = toast.oid
                           WHERE NOT att.attisdropped AND tbl.relkind = 'r' AND nspname NOT IN ('pg_catalog','information_schema')
                           GROUP BY 1,2,3,4,5,6,7,8,9,10
                       ) AS s
              ) AS s2
     ) AS s3
WHERE NOT is_na;
COMMENT ON VIEW monitor.pg_table_bloat IS 'postgres table bloat estimate';

GRANT SELECT ON monitor.pg_table_bloat TO pg_monitor;

----------------------------------------------------------------------
-- Index bloat estimate : monitor.pg_index_bloat
----------------------------------------------------------------------
DROP VIEW IF EXISTS monitor.pg_index_bloat CASCADE;
CREATE OR REPLACE VIEW monitor.pg_index_bloat AS
SELECT CURRENT_CATALOG AS datname, nspname, idxname AS relname, tblid, idxid, relpages::BIGINT * bs AS size,
       COALESCE((relpages - ( reltuples * (6 + ma - (CASE WHEN index_tuple_hdr % ma = 0 THEN ma ELSE index_tuple_hdr % ma END)
                                               + nulldatawidth + ma - (CASE WHEN nulldatawidth % ma = 0 THEN ma ELSE nulldatawidth % ma END))
                                  / (bs - pagehdr)::FLOAT  + 1 )), 0) / relpages::FLOAT AS ratio
FROM (
         SELECT nspname,idxname,indrelid AS tblid,indexrelid AS idxid,
                reltuples,relpages,
                current_setting('block_size')::INTEGER                                                               AS bs,
                (CASE WHEN version() ~ 'mingw32' OR version() ~ '64-bit|x86_64|ppc64|ia64|amd64' THEN 8 ELSE 4 END)  AS ma,
                24                                                                                                   AS pagehdr,
                (CASE WHEN max(COALESCE(pg_stats.null_frac, 0)) = 0 THEN 2 ELSE 6 END)                               AS index_tuple_hdr,
                sum((1.0 - COALESCE(pg_stats.null_frac, 0.0)) *
                    COALESCE(pg_stats.avg_width, 1024))::INTEGER                                                     AS nulldatawidth
         FROM pg_attribute
                  JOIN (
             SELECT pg_namespace.nspname,
                    ic.relname                                                   AS idxname,
                    ic.reltuples,
                    ic.relpages,
                    pg_index.indrelid,
                    pg_index.indexrelid,
                    tc.relname                                                   AS tablename,
                    regexp_split_to_table(pg_index.indkey::TEXT, ' ') :: INTEGER AS attnum,
                    pg_index.indexrelid                                          AS index_oid
             FROM pg_index
                      JOIN pg_class ic ON pg_index.indexrelid = ic.oid
                      JOIN pg_class tc ON pg_index.indrelid = tc.oid
                      JOIN pg_namespace ON pg_namespace.oid = ic.relnamespace
                      JOIN pg_am ON ic.relam = pg_am.oid
             WHERE pg_am.amname = 'btree' AND ic.relpages > 0 AND nspname NOT IN ('pg_catalog', 'information_schema')
         ) ind_atts ON pg_attribute.attrelid = ind_atts.indexrelid AND pg_attribute.attnum = ind_atts.attnum
                  JOIN pg_stats ON pg_stats.schemaname = ind_atts.nspname
             AND ((pg_stats.tablename = ind_atts.tablename AND pg_stats.attname = pg_get_indexdef(pg_attribute.attrelid, pg_attribute.attnum, TRUE))
                 OR (pg_stats.tablename = ind_atts.idxname AND pg_stats.attname = pg_attribute.attname))
         WHERE pg_attribute.attnum > 0
         GROUP BY 1, 2, 3, 4, 5, 6
     ) est;
COMMENT ON VIEW monitor.pg_index_bloat IS 'postgres index bloat estimate (btree-only)';

GRANT SELECT ON monitor.pg_index_bloat TO pg_monitor;

----------------------------------------------------------------------
-- Relation Bloat : monitor.pg_bloat
----------------------------------------------------------------------
DROP VIEW IF EXISTS monitor.pg_bloat CASCADE;
CREATE OR REPLACE VIEW monitor.pg_bloat AS
SELECT coalesce(ib.datname, tb.datname)                                                   AS datname,
       coalesce(ib.nspname, tb.nspname)                                                   AS nspname,
       coalesce(ib.tblid, tb.tblid)                                                       AS tblid,
       coalesce(tb.nspname || '.' || tb.relname, ib.nspname || '.' || ib.tblid::RegClass) AS tblname,
       tb.size                                                                            AS tbl_size,
       CASE WHEN tb.ratio < 0 THEN 0 ELSE round(tb.ratio::NUMERIC, 6) END                 AS tbl_ratio,
       (tb.size * (CASE WHEN tb.ratio < 0 THEN 0 ELSE tb.ratio::NUMERIC END)) ::BIGINT    AS tbl_wasted,
       ib.idxid,
       ib.nspname || '.' || ib.relname                                                    AS idxname,
       ib.size                                                                            AS idx_size,
       CASE WHEN ib.ratio < 0 THEN 0 ELSE round(ib.ratio::NUMERIC, 5) END                 AS idx_ratio,
       (ib.size * (CASE WHEN ib.ratio < 0 THEN 0 ELSE ib.ratio::NUMERIC END)) ::BIGINT    AS idx_wasted
FROM monitor.pg_index_bloat ib
         FULL OUTER JOIN monitor.pg_table_bloat tb ON ib.tblid = tb.tblid;

COMMENT ON VIEW monitor.pg_bloat IS 'postgres relation bloat detail';
GRANT SELECT ON monitor.pg_bloat TO pg_monitor;

----------------------------------------------------------------------
-- monitor.pg_index_bloat_human
----------------------------------------------------------------------
DROP VIEW IF EXISTS monitor.pg_index_bloat_human CASCADE;
CREATE OR REPLACE VIEW monitor.pg_index_bloat_human AS
SELECT idxname                            AS name,
       tblname,
       idx_wasted                         AS wasted,
       pg_size_pretty(idx_size)           AS idx_size,
       round(100 * idx_ratio::NUMERIC, 2) AS idx_ratio,
       pg_size_pretty(idx_wasted)         AS idx_wasted,
       pg_size_pretty(tbl_size)           AS tbl_size,
       round(100 * tbl_ratio::NUMERIC, 2) AS tbl_ratio,
       pg_size_pretty(tbl_wasted)         AS tbl_wasted
FROM monitor.pg_bloat
WHERE idxname IS NOT NULL;
COMMENT ON VIEW monitor.pg_index_bloat_human IS 'postgres index bloat info in human-readable format';
GRANT SELECT ON monitor.pg_index_bloat_human TO pg_monitor;


----------------------------------------------------------------------
-- monitor.pg_table_bloat_human
----------------------------------------------------------------------
DROP VIEW IF EXISTS monitor.pg_table_bloat_human CASCADE;
CREATE OR REPLACE VIEW monitor.pg_table_bloat_human AS
SELECT tblname                                          AS name,
       idx_wasted + tbl_wasted                          AS wasted,
       pg_size_pretty(idx_wasted + tbl_wasted)          AS all_wasted,
       pg_size_pretty(tbl_wasted)                       AS tbl_wasted,
       pg_size_pretty(tbl_size)                         AS tbl_size,
       tbl_ratio,
       pg_size_pretty(idx_wasted)                       AS idx_wasted,
       pg_size_pretty(idx_size)                         AS idx_size,
       round(idx_wasted::NUMERIC * 100.0 / idx_size, 2) AS idx_ratio
FROM (SELECT datname,
             nspname,
             tblname,
             coalesce(max(tbl_wasted), 0)                         AS tbl_wasted,
             coalesce(max(tbl_size), 1)                           AS tbl_size,
             round(100 * coalesce(max(tbl_ratio), 0)::NUMERIC, 2) AS tbl_ratio,
             coalesce(sum(idx_wasted), 0)                         AS idx_wasted,
             coalesce(sum(idx_size), 1)                           AS idx_size
      FROM monitor.pg_bloat
      WHERE tblname IS NOT NULL
      GROUP BY 1, 2, 3
     ) d;
COMMENT ON VIEW monitor.pg_table_bloat_human IS 'postgres table bloat info in human-readable format';
GRANT SELECT ON monitor.pg_table_bloat_human TO pg_monitor;


----------------------------------------------------------------------
-- Activity Overview: monitor.pg_session
----------------------------------------------------------------------
DROP VIEW IF EXISTS monitor.pg_session CASCADE;
CREATE OR REPLACE VIEW monitor.pg_session AS
SELECT coalesce(datname, 'all') AS datname, numbackends, active, idle, ixact, max_duration, max_tx_duration, max_conn_duration
FROM (
         SELECT datname,
                count(*)                                         AS numbackends,
                count(*) FILTER ( WHERE state = 'active' )       AS active,
                count(*) FILTER ( WHERE state = 'idle' )         AS idle,
                count(*) FILTER ( WHERE state = 'idle in transaction'
                    OR state = 'idle in transaction (aborted)' ) AS ixact,
                max(extract(epoch from now() - state_change))
                FILTER ( WHERE state = 'active' )                AS max_duration,
                max(extract(epoch from now() - xact_start))      AS max_tx_duration,
                max(extract(epoch from now() - backend_start))   AS max_conn_duration
         FROM pg_stat_activity
         WHERE backend_type = 'client backend'
           AND pid <> pg_backend_pid()
         GROUP BY ROLLUP (1)
         ORDER BY 1 NULLS FIRST
     ) t;
COMMENT ON VIEW monitor.pg_session IS 'postgres activity group by session';
GRANT SELECT ON monitor.pg_session TO pg_monitor;


----------------------------------------------------------------------
-- Sequential Scan: monitor.pg_seq_scan
----------------------------------------------------------------------
DROP VIEW IF EXISTS monitor.pg_seq_scan CASCADE;
CREATE OR REPLACE VIEW monitor.pg_seq_scan AS
SELECT schemaname                                                        AS nspname,
       relname,
       seq_scan,
       seq_tup_read,
       seq_tup_read / seq_scan                                           AS seq_tup_avg,
       idx_scan,
       n_live_tup + n_dead_tup                                           AS tuples,
       round(n_live_tup * 100.0::NUMERIC / (n_live_tup + n_dead_tup), 2) AS live_ratio
FROM pg_stat_user_tables
WHERE seq_scan > 0
  and (n_live_tup + n_dead_tup) > 0
ORDER BY seq_scan DESC;
COMMENT ON VIEW monitor.pg_seq_scan IS 'table that have seq scan';
GRANT SELECT ON monitor.pg_seq_scan TO pg_monitor;

7 - FAQ

frequently asked questions

ABORT due to postgres exists

This happens when you run pgsql.yml on a node with postgres running. If there’s a running postgres instance, you can explicitly remove it with pgsql-rm.yml playbook:

./pgsql-rm.yml -l <cls_to_remove>    # remove the cluster 'cls_to_remove'

ABORT due to pg_safeguard enabled

Disable pg_safeguard to remove the Postgres instance.

If pg_safeguard is enabled, you cannot remove the running pgsql instance with bin/pgsql-rm and pgsql-rm.yml playbook.

To disable pg_safeguard, you can set pg_safeguard to false in the inventory or pass -e pg_safeguard=false as cli arg to the playbook:

./pgsql-rm.yml -e pg_safeguard=false -l <cls_to_remove>    # force override pg_safeguard

Fail to wait for postgres/patroni primary

There are several possible reasons for this error, and you need to check the system logs to determine the actual cause.

This usually happens when the cluster is misconfigured, or the previous primary is improperly removed. (e.g., trash metadata in DCS with the same cluster name).

You must check /pg/log/* to find the reason.

To delete trash meta from etcd, you can use etcdctl del --prefix /pg/<cls>, do with caution!

  • 1: Misconfiguration. Identify the incorrect parameters, modify them, and apply the changes.
  • 2: Another cluster with the same cls name already exists in the deployment
  • 3: The previous cluster on the node, or previous cluster with same name was not correctly removed.
  • To remove obsolete cluster metadata, you can use etcdctl del --prefix /pg/<cls> to manually delete the residual data.
  • 4: The RPM packages related to your PostgreSQL or node were not successfully installed.
  • 5: Your Watchdog kernel module was not correctly enabled or loaded, but required.
  • 6: The locale or ctype specified pg_lc_collate and pg_lc_ctype does not exist in OS

Feel free to submit an issue or seek help from the community.


Fail to wait for postgres/patroni replica

Failed Immediately: Usually, this happens because of misconfiguration, network issues, broken DCS metadata, etc…, you have to inspect /pg/log to find out the actual reason.

Failed After a While: This may be due to source instance data corruption. Check PGSQL FAQ: How to create replicas when data is corrupted?

Timeout: If the wait for postgres replica task takes 30min or more and fails due to timeout, This is common for a huge cluster (e.g., 1TB+, which may take hours to create a replica). In this case, the underlying creating replica procedure is still proceeding. You can check cluster status with pg list <cls> and wait until the replica catches up with the primary. Then continue the following tasks:

./pgsql.yml -t pg_hba,pg_param,pg_backup,pgbouncer,pg_vip,pg_dns,pg_service,pg_exporter,pg_register -l <problematic_replica>

Install PostgreSQL 13 - 17

To install PostgreSQL 13 ~ 17, you have to set pg_version to 13, 14, 15, 16, or 17 in the inventory. (usually at cluster level)

pg_version: 17                    # install pg 17 in this template

How enable hugepage for PostgreSQL?

use node_hugepage_count and node_hugepage_ratio or /pg/bin/pg-tune-hugepage

If you plan to enable hugepage, consider using node_hugepage_count and node_hugepage_ratio and apply with ./node.yml -t node_tune .

It’s good to allocate enough hugepage before postgres start, and use pg_tune_hugepage to shrink them later.

If your postgres is already running, you can use /pg/bin/pg-tune-hugepage to enable hugepage on the fly. Note that this only works on PostgreSQL 15+

sync; echo 3 > /proc/sys/vm/drop_caches   # drop system cache (ready for performance impact)
sudo /pg/bin/pg-tune-hugepage             # write nr_hugepages to /etc/sysctl.d/hugepage.conf
pg restart <cls>                          # restart postgres to use hugepage

How to guarantee zero data loss during failover?

Use crit.yml template, or setting pg_rpo to 0, or config cluster with synchronous mode.

Consider using Sync Standby and Quorum Comit to guarantee 0 data loss during failover.


How to survive from disk full?

rm -rf /pg/dummy will free some emergency space.

The pg_dummy_filesize is set to 64MB by default. Consider increasing it to 8GB or larger in the production environment.

It will be placed on /pg/dummy same disk as the PGSQL main data disk. You can remove that file to free some emergency space. At least you can run some shell scripts on that node.


How to create replicas when data is corrupted?

Disable clonefrom on bad instances and reload patroni config.

Pigsty sets the cloneform: true tag on all instances’ patroni config, which marks the instance available for cloning replica.

If this instance has corrupt data files, you can set clonefrom: false to avoid pulling data from the evil instance. To do so:

$ vi /pg/bin/patroni.yml

tags:
  nofailover: false
  clonefrom: true      # ----------> change to false
  noloadbalance: false
  nosync: false
  version:  '15'
  spec: '4C.8G.50G'
  conf: 'oltp.yml'

$ systemctl reload patroni

How to create replicas when data is corrupted?

Disable clonefrom on bad instances and reload patroni config.

Pigsty sets the cloneform: true tag on all instances’ patroni config, which marks the instance available for cloning replica.

If this instance has corrupt data files, you can set clonefrom: false to avoid pulling data from the evil instance. To do so:

$ vi /pg/bin/patroni.yml

tags:
  nofailover: false
  clonefrom: true      # ----------> change to false
  noloadbalance: false
  nosync: false
  version:  '15'
  spec: '4C.8G.50G'
  conf: 'oltp.yml'

$ systemctl reload patroni

Performance impact of monitoring exporter

Not very much, 200ms per 10 ~ 15 seconds, won’t affect the database performance.

The default scrape interval for prometheus is 10s in pigsty, make sure the exporter can finish the scrape within that period.


How to monitor an existing PostgreSQL instance?

Check PGSQL Monitor for details.


How to remove monitor targets from prometheus?

./pgsql-rm.yml -t prometheus -l <cls>     # remove prometheus targets of cluster 'cls'

Or

bin/pgmon-rm <ins>     # shortcut for removing prometheus targets of pgsql instance 'ins'

8 - User Role

In this context, User refers to logical objects created by SQL CREATE USER / ROLE

You can manage PostgreSQL users and roles with Pigsty, in an IaC manner.


Define User

You can define roles/users with the following parameters, they are both arrays consisting of user objects:

  • pg_users : Define business users & roles at cluster level (Cluster Definition)
  • pg_default_roles : Define system-wide roles & global users (Global Defaults)

The former defines global roles and users shared across the entire environment, while the latter defines business roles and users specific to a single cluster. Here are some examples of user definitions:

pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta
    pg_databases:
      - {name: dbuser_meta     ,password: DBUser.Meta     ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: pigsty admin user }
      - {name: dbuser_view     ,password: DBUser.Viewer   ,pgbouncer: true ,roles: [dbrole_readonly] ,comment: read-only viewer for meta database }
      - {name: dbuser_grafana  ,password: DBUser.Grafana  ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: admin user for grafana database    }
      - {name: dbuser_bytebase ,password: DBUser.Bytebase ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: admin user for bytebase database   }
      - {name: dbuser_kong     ,password: DBUser.Kong     ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: admin user for kong api gateway    }
      - {name: dbuser_gitea    ,password: DBUser.Gitea    ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: admin user for gitea service       }
      - {name: dbuser_wiki     ,password: DBUser.Wiki     ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: admin user for wiki.js service     }
      - {name: dbuser_noco     ,password: DBUser.Noco     ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: admin user for nocodb service      }

User Attributes

You can customize users with more attributes, the full example is as follows:

- name: dbuser_meta           # REQUIRED, `name` is the only mandatory field of a user definition
  password: DBUser.Meta       # optional, password, can be a scram-sha-256 hash string or plain text
  login: true                 # optional, can log in, true by default  (new biz ROLE should be false)
  superuser: false            # optional, is superuser? false by default
  createdb: false             # optional, can create database? false by default
  createrole: false           # optional, can create role? false by default
  inherit: true               # optional, can this role use inherited privileges? true by default
  replication: false          # optional, can this role do replication? false by default
  bypassrls: false            # optional, can this role bypass row level security? false by default
  pgbouncer: true             # optional, add this user to pgbouncer user-list? false by default (production user should be true explicitly)
  connlimit: -1               # optional, user connection limit, default -1 disable limit
  expire_in: 3650             # optional, now + n days when this role is expired (OVERWRITE expire_at)
  expire_at: '2030-12-31'     # optional, YYYY-MM-DD 'timestamp' when this role is expired  (OVERWRITTEN by expire_in)
  comment: pigsty admin user  # optional, comment string for this user/role
  roles: [dbrole_admin]       # optional, belonged roles. default roles are: dbrole_{admin,readonly,readwrite,offline}
  parameters: {}              # optional, role level parameters with `ALTER ROLE SET`
  pool_mode: transaction      # optional, pgbouncer pool mode at user level, transaction by default
  pool_connlimit: -1          # optional, max database connections at user level, default -1 disable limit
  search_path: public         # key value config parameters, according to postgresql documentation (e.g: use pigsty as default search_path)
  • The only required field is name, which should be a valid & unique username in PostgreSQL.
  • Roles don’t need a password, while it could be necessary for a login-able user.
  • The password can be plain text or a scram-sha-256 / md5 hash string.
  • User / Role definition order matters, pg_default_roles first, pg_users later, in sequence order.
  • Make sure role / group definition is ahead of its members.
  • Role Attributes: login, superuser, createdb, createrole, inherit, replication, bypassrls
  • pgbouncer is disabled by default. Set it to true explicitly to enable it in pgbouncer.

ACL System

Pigsty has a battery-included ACL system, which can be easily used by assigning roles to users:

  • dbrole_readonly : The role for global read-only access
  • dbrole_readwrite : The role for global read-write access
  • dbrole_admin : The role for object creation
  • dbrole_offline : The role for restricted read-only access (offline instance)

If you wish to re-design your ACL system, check the following parameters and SQL templates.


Create User

Users and roles defined in pg_default_roles and pg_users will be automatically created one by one during module installation. It only runs on cluster leader, the primary instance.

To create users on an existing cluster, add new user/role definitions to all.children.<cls>.pg_users, and create the database with the bin/pgsql-user util or pgsql-user.yml playbook:

bin/pgsql-user <cls>   <dbname>         # the bin util script
bin/pgsql-user pg-meta dbuser_meta      # example: create dbuser_meta user in pg-meta cluster
./pgsql-user.yml -l <cls>   -e username=<dbname> # the actual playbook
./pgsql-user.yml -l pg-meta -e username=meta     # example: create dbuser_meta user in pg-meta cluster

Create user is an idempotent operation, meaning it can be run multiple times safely.

Create user / role with Pigsty

Pigsty will manage the pgbouncer userlist, so please create business databases with the Pigsty playbook/utils. Check create user SOP for details. If you are not using pgbouncer or able to maintain it by yourself, you can create users in any way you like.

Create owner user before create database

In PostgreSQL, users belong to the database cluster, not a specific database.

If your user is an owner of any databases, make sure the user is created before creating the database.


Modify User

Modifying PostgreSQL user attributes is the same as creating users. Adjust your user definition by modifying the config inventory, then re-run create user.

There are two exceptions: name and roles, which require manual intervention:

Rename user is not supported directly in Pigsty

The username is used as the identity of the user, so if you really want to do that, use the standard SQL:

ALTER USER "old_name" RENAME TO "new_name";
Membership will NOT be revoked by Pigsty

Note that modifying a user does not delete the user, but modifies user attributes using the ALTER USER command. It also DOES NOT revoke user permissions and group memberships, and uses the GRANT command to grant new roles.

Check PostgreSQL Docs for more details on ALTER USER.


Delete User

For security reasons, Pigsty does not automatically delete users, even if you remove user definitions from the configuration, Pigsty will not delete existing users.

You need to use the SQL command DROP USER to manually delete users:

DROP USER "<username>";

If the role you want to delete is a group (has other users belonging to it), you need to first remove other users from the group before deleting the group:

REVOKE "<rolename>" FROM "<other_user>";

If the user you want to delete owns database objects, you need to first change the ownership of these objects to another user before deleting the user:

REASSIGN OWNED BY "<username>" TO "<another_user>";

Check PostgreSQL Docs for more details on DROP USER, REASSIGN OWNED, and REVOKE.


Pgbouncer User

Pigsty helps manage users in pgbouncer userlist, and keep it in sync with the postgres. It requires explicitly setting the pgbouncer: true flag in the user definition to be enrolled in the pgbouncer user list.

The system admin user (pg_admin_username) and monitoring user (pg_monitor_username) will always be added to the pgbouncer user list for administration & monitoring.

Configuration Files

Users in the Pgbouncer connection pool are listed in /etc/pgbouncer/userlist.txt, examples:

/etc/pgbouncer/userlist.txt
"postgres" ""
"dbuser_wiki" "SCRAM-SHA-256$4096:+77dyhrPeFDT/TptHs7/7Q==$KeatuohpKIYzHPCt/tqBu85vI11o9mar/by0hHYM2W8=:X9gig4JtjoS8Y/o1vQsIX/gY1Fns8ynTXkbWOjUfbRQ="
"dbuser_view" "SCRAM-SHA-256$4096:DFoZHU/DXsHL8MJ8regdEw==$gx9sUGgpVpdSM4o6A2R9PKAUkAsRPLhLoBDLBUYtKS0=:MujSgKe6rxcIUMv4GnyXJmV0YNbf39uFRZv724+X1FE="
"dbuser_monitor" "SCRAM-SHA-256$4096:fwU97ZMO/KR0ScHO5+UuBg==$CrNsmGrx1DkIGrtrD1Wjexb/aygzqQdirTO1oBZROPY=:L8+dJ+fqlMQh7y4PmVR/gbAOvYWOr+KINjeMZ8LlFww="
"dbuser_meta" "SCRAM-SHA-256$4096:leB2RQPcw1OIiRnPnOMUEg==$eyC+NIMKeoTxshJu314+BmbMFpCcspzI3UFZ1RYfNyU=:fJgXcykVPvOfro2MWNkl5q38oz21nSl1dTtM65uYR1Q="
"dbuser_kong" "SCRAM-SHA-256$4096:bK8sLXIieMwFDz67/0dqXQ==$P/tCRgyKx9MC9LH3ErnKsnlOqgNd/nn2RyvThyiK6e4=:CDM8QZNHBdPf97ztusgnE7olaKDNHBN0WeAbP/nzu5A="
"dbuser_grafana" "SCRAM-SHA-256$4096:HjLdGaGmeIAGdWyn2gDt/Q==$jgoyOB8ugoce+Wqjr0EwFf8NaIEMtiTuQTg1iEJs9BM=:ed4HUFqLyB4YpRr+y25FBT7KnlFDnan6JPVT9imxzA4="
"dbuser_gitea" "SCRAM-SHA-256$4096:l1DBGCc4dtircZ8O8Fbzkw==$tpmGwgLuWPDog8IEKdsaDGtiPAxD16z09slvu+rHE74=:pYuFOSDuWSofpD9OZhG7oWvyAR0PQjJBffgHZLpLHds="
"dbuser_dba" "SCRAM-SHA-256$4096:zH8niABU7xmtblVUo2QFew==$Zj7/pq+ICZx7fDcXikiN7GLqkKFA+X5NsvAX6CMshF0=:pqevR2WpizjRecPIQjMZOm+Ap+x0kgPL2Iv5zHZs0+g="
"dbuser_bytebase" "SCRAM-SHA-256$4096:OMoTM9Zf8QcCCMD0svK5gg==$kMchqbf4iLK1U67pVOfGrERa/fY818AwqfBPhsTShNQ=:6HqWteN+AadrUnrgC0byr5A72noqnPugItQjOLFw0Wk="

User-level parameters are maintained in a separate file: /etc/pgbouncer/useropts.txt, examples:

/etc/pgbouncer/useropts.txt
dbuser_dba                  = pool_mode=session max_user_connections=16
dbuser_monitor              = pool_mode=session max_user_connections=8

The userlist.txt and useropts.txt will be automatically refreshed when you create users and take effect with systemctl reload pgbouncer, normally without affecting existing connections.

Reload

To reload pgbouncer configuration, you can use the ansible playbook, or systemctl command

./pgsql.yml -t pgbouncer_reload
systemctl reload pgbouncer

Admin

Pgbouncer runs with the same dbsu as PostgreSQL, defaulting to the postgres os user. You can use the pgb alias to access pgbouncer management functions using dbsu.

postgres
sudo su - postgres
pgb   # login to pgbouncer command line interface using admin user

Delete Pgbouncer User

If all database users are managed by Pigsty, you can just regenerate pgbouncer userlist (without the removed user in the list in the config inventory) and reload it:

./pgsql.yml -t pgbouncer_user,pgbouncer_reload -e pg_reload=true

To manually remove a user from the pgbouncer pool, simply delete the corresponding line from /etc/pgbouncer/userlist.txt and reload pgbouncer:

systemctl reload pgbouncer

Dynamic User Authentication

Note that the pgbouncer_auth_query parameter allows you to use dynamic queries to complete connection pool user authentication, which is a compromise when you don’t want to manage users in the connection pool.

9 - Database

In this context, Database refers to the object created by SQL CREATE DATABASE.

A PostgreSQL server can serve multiple databases simultaneously. You can manage them with Pigsty.


Define Database

Business databases are defined by pg_databases, which is a cluster-level parameter.

For example, the default meta database is defined in the pg-meta cluster:

pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta
    pg_databases:
      - { name: meta ,baseline: cmdb.sql ,comment: pigsty meta database ,schemas: [pigsty] ,extensions: [{name: postgis, schema: public}, {name: timescaledb}]}
      - { name: grafana  ,owner: dbuser_grafana  ,revokeconn: true ,comment: grafana primary database }
      - { name: bytebase ,owner: dbuser_bytebase ,revokeconn: true ,comment: bytebase primary database }
      - { name: kong     ,owner: dbuser_kong     ,revokeconn: true ,comment: kong the api gateway database }
      - { name: gitea    ,owner: dbuser_gitea    ,revokeconn: true ,comment: gitea meta database }
      - { name: wiki     ,owner: dbuser_wiki     ,revokeconn: true ,comment: wiki meta database }
      - { name: noco     ,owner: dbuser_noco     ,revokeconn: true ,comment: nocodb database }

Each database definition is a dict with the following fields:

- name: meta                      # REQUIRED, `name` is the only mandatory field of a database definition
  baseline: cmdb.sql              # optional, database sql baseline path, (relative path among ansible search path, e.g files/)
  pgbouncer: true                 # optional, add this database to pgbouncer database list? true by default
  schemas: [pigsty]               # optional, additional schemas to be created, array of schema names
  extensions:                     # optional, additional extensions to be installed: array of `{name[,schema]}`
    - { name: postgis , schema: public }
    - { name: timescaledb }
  comment: pigsty meta database   # optional, comment string for this database
  owner: postgres                 # optional, database owner, postgres by default
  template: template1             # optional, which template to use, template1 by default
  encoding: UTF8                  # optional, database encoding, UTF8 by default. (MUST same as template database)
  locale: C                       # optional, database locale, C by default.  (MUST same as template database)
  lc_collate: C                   # optional, database collate, C by default. (MUST same as template database)
  lc_ctype: C                     # optional, database ctype, C by default.   (MUST same as template database)
  tablespace: pg_default          # optional, default tablespace, 'pg_default' by default.
  allowconn: true                 # optional, allow connection, true by default. false will disable connect at all
  revokeconn: false               # optional, revoke public connection privilege. false by default. (leave connect with grant option to owner)
  register_datasource: true       # optional, register this database to grafana datasources? true by default
  connlimit: -1                   # optional, database connection limit, default -1 disable limit
  pool_auth_user: dbuser_meta     # optional, all connection to this pgbouncer database will be authenticated by this user
  pool_mode: transaction          # optional, pgbouncer pool mode at database level, default transaction
  pool_size: 64                   # optional, pgbouncer pool size at database level, default 64
  pool_size_reserve: 32           # optional, pgbouncer pool size reserve at database level, default 32
  pool_size_min: 0                # optional, pgbouncer pool size min at database level, default 0
  pool_max_db_conn: 100           # optional, max database connections at database level, default 100

The only required field is name, which should be a valid and unique database name in PostgreSQL.

Newly created databases are forked from template1 database by default. Which is customized by PG_PROVISION during cluster bootstrap.

Check ACL: Database Privilege for details about database-level privilege.


Create Database

Databases defined in pg_databases will be automatically created during module installation. If you wish to create database on an existing cluster, the bin/pgsql-db util can be used.

Add new database definition to all.children.<cls>.pg_databases, and create that database with:

bin/pgsql-db <cls> <dbname>    # the bin util script
bin/pgsql-db pg-meta meta      # example: create meta database in pg-meta cluster
./pgsql-db.yml -l <cls> -e dbname=<dbname>    # the actual playbook
./pgsql-db.yml -l pg-meta -e dbname=meta      # example: create meta database in pg-meta cluster

This playbook is usually idempotent and can be re-run to flush the database definition. But if you have non-trivial baseline schema (like drop stuff), you should NOT re-run this on existing databases.

Create postgres database with pigsty

Pigsty will manage pgbouncer database list, so please create business databases with the Pigsty playbook/utils. Check create database SOP for details. If you are not using pgbouncer or able to maintain it by yourself, you can create databases in any way you like.

Create owner before create database

If your database has a non-trivial owner (dbsu postgres by default), make sure the owner user exists before creating the database. In short, always create the users before creating databases.


Pgbouncer Database

Pgbouncer is enabled by default and serves as connection pool middleware.

Pigsty will add all databases in pg_databases to the pgbouncer database list by default. You can disable the pgbouncer proxy for a specific database by setting pgbouncer: false in the database definition.

The Pgbouncer database list will be updated when create database with Pigsty util & playbook. Databases are listed in /etc/pgbouncer/database.txt, with extra database-level parameters:

/etc/pgbouncer/database.txt
meta     = host=/var/run/postgresql mode=session
grafana  = host=/var/run/postgresql mode=transaction
bytebase = host=/var/run/postgresql auth_user=dbuser_meta
kong     = host=/var/run/postgresql pool_size=32 reserve_pool=64
gitea    = host=/var/run/postgresql min_pool_size=10
wiki     = host=/var/run/postgresql
noco     = host=/var/run/postgresql
mongo    = host=/var/run/postgresql

When you create databases, the Pgbouncer database list definition file will be refreshed and take effect through online configuration reload, without affecting existing connections.

To access pgbouncer admin functionality, you can use the pgb alias as dbsu (postgres). Check pgbouncer usage for available commands:

postgres
sudo su - postgres  # switch to the postgres dbsu
pgb                 # access the pgbouncer admin virtual database

There’s a util function defined in /etc/profile.d/pg-alias.sh, allowing you to reroute pgbouncer database traffic to a new host quickly, which can be used during zero-downtime migration.

/etc/profile.d/pg-alias.sh
# route pgbouncer traffic to another cluster member
function pgb-route(){
  local ip=${1-'\/var\/run\/postgresql'}
  sed -ie "s/host=[^[:space:]]\+/host=${ip}/g" /etc/pgbouncer/pgbouncer.ini
  cat /etc/pgbouncer/pgbouncer.ini
}

10 - Service

reliable service access via lb, proxy, pool

Service Implementation

In Pigsty, services are implemented using haproxy on nodes, differentiated by different ports on the host node.

Every node has Haproxy enabled to expose services. From the database perspective, nodes in the cluster may be primary or replicas, but from the service perspective, all nodes are the same. This means even if you access a replica node, as long as you use the correct service port, you can still use the primary’s read-write service. This design seals the complexity: as long as you can access any instance on the PostgreSQL cluster, you can fully access all services.

This design is akin to the NodePort service in Kubernetes. Similarly, in Pigsty, every service includes these two core elements:

  1. Access endpoints exposed via NodePort (port number, from where to access?)
  2. Target instances chosen through Selectors (list of instances, who will handle it?)

The boundary of Pigsty’s service delivery stops at the cluster’s HAProxy. Users can access these load balancers in various ways. Please refer to Access Service.

All services are declared through configuration files. For instance, the default PostgreSQL service is defined by the pg_default_services parameter:

- { name: primary ,port: 5433 ,dest: default  ,check: /primary   ,selector: "[]" }
- { name: replica ,port: 5434 ,dest: default  ,check: /read-only ,selector: "[]" , backup: "[? pg_role == `primary` || pg_role == `offline` ]" }
- { name: default ,port: 5436 ,dest: postgres ,check: /primary   ,selector: "[]" }
- { name: offline ,port: 5438 ,dest: postgres ,check: /replica   ,selector: "[? pg_role == `offline` || pg_offline_query ]" , backup: "[? pg_role == `replica` && !pg_offline_query]"}

You can also define new service in pg_services. And pg_default_services and pg_services are both arrays of Service Definition.


Define Services

The default services are defined in pg_default_services.

While you can define your extra PostgreSQL services with pg_services @ the global or cluster level.

These two parameters are both arrays of service objects. Each service definition will be rendered as a haproxy config in /etc/haproxy/<svcname>.cfg, check service.cfg for details.

Here is an example of an extra service definition: standby

- name: standby                   # required, service name, the actual svc name will be prefixed with `pg_cluster`, e.g: pg-meta-standby
  port: 5435                      # required, service exposed port (work as kubernetes service node port mode)
  ip: "*"                         # optional, service bind ip address, `*` for all ip by default
  selector: "[]"                  # required, service member selector, use JMESPath to filter inventory
  dest: default                   # optional, destination port, default|postgres|pgbouncer|<port_number>, 'default' by default
  check: /sync                    # optional, health check url path, / by default
  backup: "[? pg_role == `primary`]"  # backup server selector
  maxconn: 3000                   # optional, max allowed front-end connection
  balance: roundrobin             # optional, haproxy load balance algorithm (roundrobin by default, other: leastconn)
  options: 'inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100'

And it will be translated to a haproxy config file /etc/haproxy/pg-test-standby.conf:

#---------------------------------------------------------------------
# service: pg-test-standby @ 10.10.10.11:5435
#---------------------------------------------------------------------
# service instances 10.10.10.11, 10.10.10.13, 10.10.10.12
# service backups   10.10.10.11
listen pg-test-standby
    bind *:5435
    mode tcp
    maxconn 5000
    balance roundrobin
    option httpchk
    option http-keep-alive
    http-check send meth OPTIONS uri /sync  # <--- true for primary & sync standby
    http-check expect status 200
    default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
    # servers
    server pg-test-1 10.10.10.11:6432 check port 8008 weight 100 backup   # the primary is used as backup server
    server pg-test-3 10.10.10.13:6432 check port 8008 weight 100
    server pg-test-2 10.10.10.12:6432 check port 8008 weight 100

Reload Service

When cluster membership has changed, such as append / remove replicas, switchover/failover, or adjust relative weight, You have to reload service to make the changes take effect.

bin/pgsql-svc <cls> [ip...]         # reload service for lb cluster or lb instance
# ./pgsql.yml -t pg_service         # the actual ansible task to reload service

Override Service

You can override the default service configuration in several ways:

Bypass Pgbouncer

When defining a service, if svc.dest='default', this parameter pg_default_service_dest will be used as the default value. pgbouncer is used by default, you can use postgres instead, so the default primary & replica service will bypass pgbouncer and route traffic to postgres directly

If you don’t need connection pooling at all, you can change pg_default_service_dest to postgres, and remove default and offline services.

If you don’t need read-only replicas for online traffic, you can remove replica from pg_default_services too.

pg_default_services:
  - { name: primary ,port: 5433 ,dest: default  ,check: /primary   ,selector: "[]" }
  - { name: replica ,port: 5434 ,dest: default  ,check: /read-only ,selector: "[]" , backup: "[? pg_role == `primary` || pg_role == `offline` ]" }
  - { name: default ,port: 5436 ,dest: postgres ,check: /primary   ,selector: "[]" }
  - { name: offline ,port: 5438 ,dest: postgres ,check: /replica   ,selector: "[? pg_role == `offline` || pg_offline_query ]" , backup: "[? pg_role == `replica` && !pg_offline_query]"}

Delegate Service

Pigsty exposes PostgreSQL services with haproxy on node. All haproxy instances among the cluster are configured with the same service definition.

However, you can delegate pg service to a specific node group (e.g., dedicate haproxy lb cluster) rather than cluster members.

To do so, you will have to override the default service definition with pg_default_services and set pg_service_provider to the proxy group name.

For example, this configuration will expose pg cluster primary service on haproxy node group proxy with port 10013.

pg_service_provider: proxy       # use load balancer on group `proxy` with port 10013
pg_default_services:  [{ name: primary ,port: 10013 ,dest: postgres  ,check: /primary   ,selector: "[]" }]

It’s user’s responsibility to make sure each delegate service port is unique among the proxy cluster.

Split read & write, route traffic to the right place, and achieve stable & reliable access to the PostgreSQL cluster.

Service is an abstraction to seal the details of the underlying cluster, especially during cluster failover/switchover.


Personal User

Service is meaningless to personal users. You can access the database with raw IP address or whatever method you like.

psql postgres://dbuser_dba:[email protected]/meta     # dbsu direct connect
psql postgres://dbuser_meta:[email protected]/meta   # default business admin user
psql postgres://dbuser_view:DBUser.View@pg-meta/meta       # default read-only user

Service Overview

We utilize a PostgreSQL database cluster based on replication in real-world production environments. Within the cluster, only one instance is the leader (primary) that can accept writes. Other instances (replicas) continuously fetch WAL from the leader to stay synchronized. Additionally, replicas can handle read-only queries and offload the primary in read-heavy, write-light scenarios. Thus, distinguishing between write and read-only requests is a common practice.

Moreover, we pool requests through a connection pooling middleware (Pgbouncer) for high-frequency, short-lived connections to reduce the overhead of connection and backend process creation. And, for scenarios like ETL and change execution, we need to bypass the connection pool and directly access the database servers. Furthermore, high-availability clusters may undergo failover during failures, causing a change in the cluster leadership. Therefore, the RW requests should be re-routed automatically to the new leader.

These varied requirements (read-write separation, pooling vs. direct connection, and client request failover) have led to the abstraction of the service concept.

Typically, a database cluster must provide this basic service:

  • Read-write service (primary): Can read and write to the database.

For production database clusters, at least these two services should be provided:

  • Read-write service (primary): Write data: Only carried by the primary.
  • Read-only service (replica): Read data: Can be carried by replicas, but fallback to the primary if no replicas are available.

Additionally, there might be other services, such as:

  • Direct access service (default): Allows (admin) users to bypass the connection pool and directly access the database.
  • Offline replica service (offline): A dedicated replica that doesn’t handle online read traffic, used for ETL and analytical queries.
  • Synchronous replica service (standby): A read-only service with no replication delay, handled by synchronous standby/primary for read queries.
  • Delayed replica service (delayed): Accesses older data from the same cluster from a certain time ago, handled by delayed replicas.

Default Service

Pigsty will enable four default services for each PostgreSQL cluster:

service port description
primary 5433 pgbouncer read/write, connect to primary 5432 or 6432
replica 5434 pgbouncer read-only, connect to replicas 5432/6432
default 5436 admin or direct access to primary
offline 5438 OLAP, ETL, personal user, interactive queries

Take the default pg-meta cluster as an example, you can access these services in the following ways:

psql postgres://dbuser_meta:DBUser.Meta@pg-meta:5433/meta   # pg-meta-primary : production read/write via primary pgbouncer(6432)
psql postgres://dbuser_meta:DBUser.Meta@pg-meta:5434/meta   # pg-meta-replica : production read-only via replica pgbouncer(6432)
psql postgres://dbuser_dba:DBUser.DBA@pg-meta:5436/meta     # pg-meta-default : Direct connect primary via primary postgres(5432)
psql postgres://dbuser_stats:DBUser.Stats@pg-meta:5438/meta # pg-meta-offline : Direct connect offline via offline postgres(5432)

pigsty-ha.png

Here the pg-meta domain name is point to the cluster’s L2 VIP, which in turn points to the haproxy load balancer on the primary instance. It is responsible for routing traffic to different instances, check Access Services for details.


Primary Service

The primary service may be the most critical service for production usage.

It will route traffic to the primary instance, depending on pg_default_service_dest:

  • pgbouncer: route traffic to primary pgbouncer port (6432), which is the default behavior
  • postgres: route traffic to primary postgres port (5432) directly if you don’t want to use pgbouncer
- { name: primary ,port: 5433 ,dest: default  ,check: /primary   ,selector: "[]" }

It means all cluster members will be included in the primary service (selector: "[]"), but the one and only one instance that past health check (check: /primary) will be used as the primary instance. Patroni will guarantee that only one instance is primary at any time, so the primary service will always route traffic to THE primary instance.

listen pg-test-primary
    bind *:5433
    mode tcp
    maxconn 5000
    balance roundrobin
    option httpchk
    option http-keep-alive
    http-check send meth OPTIONS uri /primary
    http-check expect status 200
    default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
    # servers
    server pg-test-1 10.10.10.11:6432 check port 8008 weight 100
    server pg-test-3 10.10.10.13:6432 check port 8008 weight 100
    server pg-test-2 10.10.10.12:6432 check port 8008 weight 100

Replica Service

The replica service is used for production read-only traffic.

There may be many more read-only queries than read-write queries in real-world scenarios. You may have many replicas.

The replica service will route traffic to Pgbouncer or postgres depending on pg_default_service_dest, just like the primary service.

- { name: replica ,port: 5434 ,dest: default  ,check: /read-only ,selector: "[]" , backup: "[? pg_role == `primary` || pg_role == `offline` ]" }

The replica service traffic will try to use common pg instances with pg_role = replica to alleviate the load on the primary instance as much as possible. It will try NOT to use instances with pg_role = offline to avoid mixing OLAP & OLTP queries as much as possible.

All cluster members will be included in the replica service (selector: "[]") when it passes the read-only health check (check: /read-only). primary and offline instances are used as backup servers, which will take over in case of all replica instances are down.

listen pg-test-replica
    bind *:5434
    mode tcp
    maxconn 5000
    balance roundrobin
    option httpchk
    option http-keep-alive
    http-check send meth OPTIONS uri /read-only
    http-check expect status 200
    default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
    # servers
    server pg-test-1 10.10.10.11:6432 check port 8008 weight 100 backup
    server pg-test-3 10.10.10.13:6432 check port 8008 weight 100
    server pg-test-2 10.10.10.12:6432 check port 8008 weight 100

Default Service

The default service will route to primary postgres (5432) by default.

It is quite like the primary service, except it will always bypass pgbouncer, regardless of pg_default_service_dest. Which is useful for administration connection, ETL writes, CDC changing data capture, etc…

- { name: primary ,port: 5433 ,dest: default  ,check: /primary   ,selector: "[]" }
listen pg-test-default
    bind *:5436
    mode tcp
    maxconn 5000
    balance roundrobin
    option httpchk
    option http-keep-alive
    http-check send meth OPTIONS uri /primary
    http-check expect status 200
    default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
    # servers
    server pg-test-1 10.10.10.11:5432 check port 8008 weight 100
    server pg-test-3 10.10.10.13:5432 check port 8008 weight 100
    server pg-test-2 10.10.10.12:5432 check port 8008 weight 100

Offline Service

The Offline service will route traffic to dedicate postgres instance directly.

Which could be a pg_role = offline instance, or a pg_offline_query flagged instance.

If no such instance is found, it will fall back to any replica instances. the bottom line is: it will never route traffic to the primary instance.

- { name: offline ,port: 5438 ,dest: postgres ,check: /replica   ,selector: "[? pg_role == `offline` || pg_offline_query ]" , backup: "[? pg_role == `replica` && !pg_offline_query]"}
listen pg-test-offline
    bind *:5438
    mode tcp
    maxconn 5000
    balance roundrobin
    option httpchk
    option http-keep-alive
    http-check send meth OPTIONS uri /replica
    http-check expect status 200
    default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
    # servers
    server pg-test-3 10.10.10.13:5432 check port 8008 weight 100
    server pg-test-2 10.10.10.12:5432 check port 8008 weight 100 backup

Access Service

Pigsty exposes service with haproxy. Which is enabled on all nodes by default.

haproxy load balancers are idempotent among the same pg cluster by default, you use ANY / ALL of them by all means.

The typical method is access via cluster domain name, which resolves to cluster L2 VIP, or all instances ip address in a round-robin manner.

Service can be implemented in different ways. You can even implement your own access method such as L4 LVS, F5, etc… instead of haproxy.

pigsty-access.jpg

You can use a different combination of host & port, they are providing PostgreSQL service in different ways.

Host

type sample description
Cluster Domain Name pg-test via cluster domain name (resolved by dnsmasq @ infra nodes)
Cluster VIP Address 10.10.10.3 via a L2 VIP address managed by vip-manager, bind to primary
Instance Hostname pg-test-1 Access via any instance hostname (resolved by dnsmasq @ infra nodes)
Instance IP Address 10.10.10.11 Access any instance ip address

Port

Pigsty uses different ports to distinguish between pg services:

port service type description
5432 postgres database Direct access to postgres server
6432 pgbouncer middleware Go through connection pool middleware before postgres
5433 primary service Access primary pgbouncer (or postgres)
5434 replica service Access replica pgbouncer (or postgres)
5436 default service Access primary postgres
5438 offline service Access offline postgres

Combinations

# Access via cluster domain
postgres://test@pg-test:5432/test # DNS -> L2 VIP -> primary direct connection
postgres://test@pg-test:6432/test # DNS -> L2 VIP -> primary connection pool -> primary
postgres://test@pg-test:5433/test # DNS -> L2 VIP -> HAProxy -> Primary Connection Pool -> Primary
postgres://test@pg-test:5434/test # DNS -> L2 VIP -> HAProxy -> Replica Connection Pool -> Replica
postgres://dbuser_dba@pg-test:5436/test # DNS -> L2 VIP -> HAProxy -> Primary direct connection (for Admin)
postgres://dbuser_stats@pg-test:5438/test # DNS -> L2 VIP -> HAProxy -> offline direct connection (for ETL/personal queries)

# Direct access via cluster VIP
postgres://[email protected]:5432/test # L2 VIP -> Primary direct access
postgres://[email protected]:6432/test # L2 VIP -> Primary Connection Pool -> Primary
postgres://[email protected]:5433/test # L2 VIP -> HAProxy -> Primary Connection Pool -> Primary
postgres://[email protected]:5434/test # L2 VIP -> HAProxy -> Repilca Connection Pool -> Replica
postgres://[email protected]:5436/test # L2 VIP -> HAProxy -> Primary direct connection (for Admin)
postgres://[email protected]::5438/test # L2 VIP -> HAProxy -> offline direct connect (for ETL/personal queries)

# Specify any cluster instance name directly
postgres://test@pg-test-1:5432/test # DNS -> Database Instance Direct Connect (singleton access)
postgres://test@pg-test-1:6432/test # DNS -> connection pool -> database
postgres://test@pg-test-1:5433/test # DNS -> HAProxy -> connection pool -> database read/write
postgres://test@pg-test-1:5434/test # DNS -> HAProxy -> connection pool -> database read-only
postgres://dbuser_dba@pg-test-1:5436/test # DNS -> HAProxy -> database direct connect
postgres://dbuser_stats@pg-test-1:5438/test # DNS -> HAProxy -> database offline read/write

# Directly specify any cluster instance IP access
postgres://[email protected]:5432/test # Database instance direct connection (directly specify instance, no automatic traffic distribution)
postgres://[email protected]:6432/test # Connection Pool -> Database
postgres://[email protected]:5433/test # HAProxy -> connection pool -> database read/write
postgres://[email protected]:5434/test # HAProxy -> connection pool -> database read-only
postgres://[email protected]:5436/test # HAProxy -> Database Direct Connections
postgres://[email protected]:5438/test # HAProxy -> database offline read-write

# Smart client automatic read/write separation (connection pooling)
postgres://[email protected]:6432,10.10.10.12:6432,10.10.10.13:6432/test?target_session_attrs=primary
postgres://[email protected]:6432,10.10.10.12:6432,10.10.10.13:6432/test?target_session_attrs=prefer-standby

11 - Auth / HBA

Host-Based Authentication in Pigsty

PostgreSQL has various authentication methods. You can use all of them, while pigsty’s battery-included ACL system focuses on HBA, password, and SSL authentication.


Client Authentication

To connect to a PostgreSQL database, the user has to be authenticated (with a password by default).

You can provide the password in the connection string (not secure) or use the PGPASSWORD env or .pgpass file. Check psql docs and PostgreSQL connection string for more details.

psql 'host=<host> port=<port> dbname=<dbname> user=<username> password=<password>'
psql postgres://<username>:<password>@<host>:<port>/<dbname>
PGPASSWORD=<password>; psql -U <username> -h <host> -p <port> -d <dbname>

The default connection string for the meta database:

psql 'host=10.10.10.10 port=5432 dbname=meta user=dbuser_dba password=DBUser.DBA'
psql postgres://dbuser_dba:[email protected]:5432/meta
PGPASSWORD=DBUser.DBA; psql -U dbuser_dba -h 10.10.10.10 -p 5432 -d meta

To connect with the SSL certificate, you can use the PGSSLCERT and PGSSLKEY env or sslkey & sslcert parameters.

psql 'postgres://dbuser_dba:[email protected]:5432/meta?sslkey=/path/to/dbuser_dba.key&sslcert=/path/to/dbuser_dba.crt'

While the client certificate (CN = username) can be issued with local CA & cert.yml.


Define HBA

There are four parameters for HBA Rules in Pigsty:

Which are array of hba rule objects, and each hba rule is one of the following forms:

1. Raw Form

- title: allow intranet password access
  role: common
  rules:
    - host   all  all  10.0.0.0/8      md5
    - host   all  all  172.16.0.0/12   md5
    - host   all  all  192.168.0.0/16  md5

In the form, the title will be rendered as a comment line, followed by the rules as hba string one by one.

An HBA Rule is installed when the instance’s pg_role is the same as the role.

HBA Rule with role: common will be installed on all instances.

HBA Rule with role: offline will be installed on instances with pg_role = offline or pg_offline_query = true.

2. Alias Form

The alias form, which replace rules with addr, auth, user, and db fields.

- addr: 'intra'    # world|intra|infra|admin|local|localhost|cluster|<cidr>
  auth: 'pwd'      # trust|pwd|ssl|cert|deny|<official auth method>
  user: 'all'      # all|${dbsu}|${repl}|${admin}|${monitor}|<user>|<group>
  db: 'all'        # all|replication|....
  rules: []        # raw hba string precedence over above all
  title: allow intranet password access
  • addr: where

    • world: all IP addresses
    • intra: all intranet cidr: '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'
    • infra: IP addresses of infra nodes
    • admin: admin_ip address
    • local: local unix socket
    • localhost: local unix socket + tcp 127.0.0.1/32
    • cluster: all IP addresses of pg cluster members
    • <cidr>: any standard CIDR blocks or IP addresses
  • auth: how

    • deny: reject access
    • trust: trust authentication
    • pwd: use md5 or scram-sha-256 password auth according to pg_pwd_enc
    • sha/scram-sha-256: enforce scram-sha-256 password authentication
    • md5: md5 password authentication
    • ssl: enforce host ssl in addition to pwd auth
    • ssl-md5: enforce host ssl in addition to md5 password auth
    • ssl-sha: enforce host ssl in addition to scram-sha-256 password auth
    • os/ident: use ident os user authentication
    • peer: use peer authentication
    • cert: use certificate-based client authentication
  • user: who

  • db: which

    • all: all databases
    • replication: replication database
    • ad hoc database name

3. Where to Define

Typically, global HBA is defined in all.vars. If you want to modify the global default HBA rules, you can copy from the full.yml template to all.vars for modification.

Cluster-specific HBA rules are defined in the cluster-level configuration of the database:

Here are some examples of cluster HBA rule definitions.

pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta
    pg_hba_rules:
      - { user: dbuser_view ,db: all    ,addr: infra        ,auth: pwd  ,title: 'allow grafana dashboard access cmdb from infra nodes'}
      - { user: all         ,db: all    ,addr: 100.0.0.0/8  ,auth: pwd  ,title: 'all user access all db from kubernetes cluster' }
      - { user: '${admin}'  ,db: world  ,addr: 0.0.0.0/0    ,auth: cert ,title: 'all admin world access with client cert'        }

Reload HBA

To reload postgres/pgbouncer hba rules:

bin/pgsql-hba <cls>                 # reload hba rules of cluster `<cls>`
bin/pgsql-hba <cls> ip1 ip2...      # reload hba rules of specific instances

The underlying command: are:

./pgsql.yml -l <cls> -e pg_reload=true -t pg_hba,pg_reload
./pgsql.yml -l <cls> -e pg_reload=true -t pgbouncer_hba,pgbouncer_reload

Default HBA

Pigsty has a default set of HBA rules, which is pretty secure for most cases.

The rules are self-explained in alias form.

pg_default_hba_rules:             # postgres default host-based authentication rules
  - {user: '${dbsu}'    ,db: all         ,addr: local     ,auth: ident ,title: 'dbsu access via local os user ident'  }
  - {user: '${dbsu}'    ,db: replication ,addr: local     ,auth: ident ,title: 'dbsu replication from local os ident' }
  - {user: '${repl}'    ,db: replication ,addr: localhost ,auth: pwd   ,title: 'replicator replication from localhost'}
  - {user: '${repl}'    ,db: replication ,addr: intra     ,auth: pwd   ,title: 'replicator replication from intranet' }
  - {user: '${repl}'    ,db: postgres    ,addr: intra     ,auth: pwd   ,title: 'replicator postgres db from intranet' }
  - {user: '${monitor}' ,db: all         ,addr: localhost ,auth: pwd   ,title: 'monitor from localhost with password' }
  - {user: '${monitor}' ,db: all         ,addr: infra     ,auth: pwd   ,title: 'monitor from infra host with password'}
  - {user: '${admin}'   ,db: all         ,addr: infra     ,auth: ssl   ,title: 'admin @ infra nodes with pwd & ssl'   }
  - {user: '${admin}'   ,db: all         ,addr: world     ,auth: ssl   ,title: 'admin @ everywhere with ssl & pwd'   }
  - {user: '+dbrole_readonly',db: all    ,addr: localhost ,auth: pwd   ,title: 'pgbouncer read/write via local socket'}
  - {user: '+dbrole_readonly',db: all    ,addr: intra     ,auth: pwd   ,title: 'read/write biz user via password'     }
  - {user: '+dbrole_offline' ,db: all    ,addr: intra     ,auth: pwd   ,title: 'allow etl offline tasks from intranet'}
pgb_default_hba_rules:            # pgbouncer default host-based authentication rules
  - {user: '${dbsu}'    ,db: pgbouncer   ,addr: local     ,auth: peer  ,title: 'dbsu local admin access with os ident'}
  - {user: 'all'        ,db: all         ,addr: localhost ,auth: pwd   ,title: 'allow all user local access with pwd' }
  - {user: '${monitor}' ,db: pgbouncer   ,addr: intra     ,auth: pwd   ,title: 'monitor access via intranet with pwd' }
  - {user: '${monitor}' ,db: all         ,addr: world     ,auth: deny  ,title: 'reject all other monitor access addr' }
  - {user: '${admin}'   ,db: all         ,addr: intra     ,auth: pwd   ,title: 'admin access via intranet with pwd'   }
  - {user: '${admin}'   ,db: all         ,addr: world     ,auth: deny  ,title: 'reject all other admin access addr'   }
  - {user: 'all'        ,db: all         ,addr: intra     ,auth: pwd   ,title: 'allow all user intra access with pwd' }

Security Enhancement

For those critical cases, we have a safe.yml template with the following hba rule set as a reference:

pg_default_hba_rules:             # postgres host-based auth rules by default
  - {user: '${dbsu}'    ,db: all         ,addr: local     ,auth: ident ,title: 'dbsu access via local os user ident'  }
  - {user: '${dbsu}'    ,db: replication ,addr: local     ,auth: ident ,title: 'dbsu replication from local os ident' }
  - {user: '${repl}'    ,db: replication ,addr: localhost ,auth: ssl   ,title: 'replicator replication from localhost'}
  - {user: '${repl}'    ,db: replication ,addr: intra     ,auth: ssl   ,title: 'replicator replication from intranet' }
  - {user: '${repl}'    ,db: postgres    ,addr: intra     ,auth: ssl   ,title: 'replicator postgres db from intranet' }
  - {user: '${monitor}' ,db: all         ,addr: localhost ,auth: pwd   ,title: 'monitor from localhost with password' }
  - {user: '${monitor}' ,db: all         ,addr: infra     ,auth: ssl   ,title: 'monitor from infra host with password'}
  - {user: '${admin}'   ,db: all         ,addr: infra     ,auth: ssl   ,title: 'admin @ infra nodes with pwd & ssl'   }
  - {user: '${admin}'   ,db: all         ,addr: world     ,auth: cert  ,title: 'admin @ everywhere with ssl & cert'   }
  - {user: '+dbrole_readonly',db: all    ,addr: localhost ,auth: ssl   ,title: 'pgbouncer read/write via local socket'}
  - {user: '+dbrole_readonly',db: all    ,addr: intra     ,auth: ssl   ,title: 'read/write biz user via password'     }
  - {user: '+dbrole_offline' ,db: all    ,addr: intra     ,auth: ssl   ,title: 'allow etl offline tasks from intranet'}
pgb_default_hba_rules:            # pgbouncer host-based authentication rules
  - {user: '${dbsu}'    ,db: pgbouncer   ,addr: local     ,auth: peer  ,title: 'dbsu local admin access with os ident'}
  - {user: 'all'        ,db: all         ,addr: localhost ,auth: pwd   ,title: 'allow all user local access with pwd' }
  - {user: '${monitor}' ,db: pgbouncer   ,addr: intra     ,auth: ssl   ,title: 'monitor access via intranet with pwd' }
  - {user: '${monitor}' ,db: all         ,addr: world     ,auth: deny  ,title: 'reject all other monitor access addr' }
  - {user: '${admin}'   ,db: all         ,addr: intra     ,auth: ssl   ,title: 'admin access via intranet with pwd'   }
  - {user: '${admin}'   ,db: all         ,addr: world     ,auth: deny  ,title: 'reject all other admin access addr'   }
  - {user: 'all'        ,db: all         ,addr: intra     ,auth: ssl   ,title: 'allow all user intra access with pwd' }

12 - Privileges

Access Control with default roles and privileges

Pigsty has a default role system consisting of four default roles and four default users:

Default User User Description Default Role Role Description
postgres system superuser dbrole_readonly role for global read-only access
replicator system replicator dbrole_readwrite role for global read-write access
dbuser_dba pgsql admin user dbrole_admin role for object creation
dbuser_monitor pgsql monitor user dbrole_offline role for restricted read-only access

Summary

Role name Attributes Member of Description
dbrole_readonly NOLOGIN role for global read-only access
dbrole_readwrite NOLOGIN dbrole_readonly role for global read-write access
dbrole_admin NOLOGIN pg_monitor,dbrole_readwrite role for object creation
dbrole_offline NOLOGIN role for restricted read-only access
postgres SUPERUSER system superuser
replicator REPLICATION pg_monitor,dbrole_readonly system replicator
dbuser_dba SUPERUSER dbrole_admin pgsql admin user
dbuser_monitor pg_monitor pgsql monitor user
pg_default_roles:                 # default roles and users in postgres cluster
  - { name: dbrole_readonly  ,login: false ,comment: role for global read-only access     }
  - { name: dbrole_offline   ,login: false ,comment: role for restricted read-only access }
  - { name: dbrole_readwrite ,login: false ,roles: [dbrole_readonly] ,comment: role for global read-write access }
  - { name: dbrole_admin     ,login: false ,roles: [pg_monitor, dbrole_readwrite] ,comment: role for object creation }
  - { name: postgres     ,superuser: true  ,comment: system superuser }
  - { name: replicator ,replication: true  ,roles: [pg_monitor, dbrole_readonly] ,comment: system replicator }
  - { name: dbuser_dba   ,superuser: true  ,roles: [dbrole_admin]  ,pgbouncer: true ,pool_mode: session, pool_connlimit: 16 ,comment: pgsql admin user }
  - { name: dbuser_monitor ,roles: [pg_monitor] ,pgbouncer: true ,parameters: {log_min_duration_statement: 1000 } ,pool_mode: session ,pool_connlimit: 8 ,comment: pgsql monitor user }

Default Roles

There are four default roles in pigsty:

  • Read-Only (dbrole_readonly): Role for global read-only access
  • Read-Write (dbrole_readwrite): Role for global read-write access, inherits dbrole_readonly.
  • Admin (dbrole_admin): Role for DDL commands, inherits dbrole_readwrite.
  • Offline (dbrole_offline): Role for restricted read-only access (offline instance)

Default roles are defined in pg_default_roles, changing default roles is not recommended.

- { name: dbrole_readonly  , login: false , comment: role for global read-only access  }                            # production read-only role
- { name: dbrole_offline ,   login: false , comment: role for restricted read-only access (offline instance) }      # restricted-read-only role
- { name: dbrole_readwrite , login: false , roles: [dbrole_readonly], comment: role for global read-write access }  # production read-write role
- { name: dbrole_admin , login: false , roles: [pg_monitor, dbrole_readwrite] , comment: role for object creation } # production DDL change role

Default Users

There are four default users in pigsty, too.

  • Superuser (postgres), the owner and creator of the cluster, same as the OS dbsu.
  • Replication user (replicator), the system user used for primary-replica.
  • Monitor user (dbuser_monitor), a user used to monitor database and connection pool metrics.
  • Admin user (dbuser_dba), the admin user who performs daily operations and database changes.

Default users’ username/password are defined with dedicated parameters (except for dbsu password):

!> Remember to change these password in production deployment !

pg_dbsu: postgres                             # os user for the database
pg_replication_username: replicator           # system replication user
pg_replication_password: DBUser.Replicator    # system replication password
pg_monitor_username: dbuser_monitor           # system monitor user
pg_monitor_password: DBUser.Monitor           # system monitor password
pg_admin_username: dbuser_dba                 # system admin user
pg_admin_password: DBUser.DBA                 # system admin password

To define extra options, specify them in pg_default_roles:

- { name: postgres     ,superuser: true                                          ,comment: system superuser }
- { name: replicator ,replication: true  ,roles: [pg_monitor, dbrole_readonly]   ,comment: system replicator }
- { name: dbuser_dba   ,superuser: true  ,roles: [dbrole_admin]  ,pgbouncer: true ,pool_mode: session, pool_connlimit: 16 , comment: pgsql admin user }
- { name: dbuser_monitor   ,roles: [pg_monitor, dbrole_readonly] ,pgbouncer: true ,parameters: {log_min_duration_statement: 1000 } ,pool_mode: session ,pool_connlimit: 8 ,comment: pgsql monitor user }

Privileges

Pigsty has a battery-included privilege model that works with default roles.

  • All users have access to all schemas.
  • Read-Only user can read from all tables. (SELECT, EXECUTE)
  • Read-Write user can write to all tables run DML. (INSERT, UPDATE, DELETE).
  • Admin user can create object and run DDL (CREATE, USAGE, TRUNCATE, REFERENCES, TRIGGER).
  • Offline user is Read-Only user with limited access on offline instance (pg_role = 'offline' or pg_offline_query = true)
  • Object created by admin users will have correct privilege.
  • Default privileges are installed on all databases, including template database.
  • Database connect privilege is covered by database definition
  • CREATE privileges of database & public schema are revoked from PUBLIC by default

Object Privilege

Default object privileges are defined in pg_default_privileges.

- GRANT USAGE      ON SCHEMAS   TO dbrole_readonly
- GRANT SELECT     ON TABLES    TO dbrole_readonly
- GRANT SELECT     ON SEQUENCES TO dbrole_readonly
- GRANT EXECUTE    ON FUNCTIONS TO dbrole_readonly
- GRANT USAGE      ON SCHEMAS   TO dbrole_offline
- GRANT SELECT     ON TABLES    TO dbrole_offline
- GRANT SELECT     ON SEQUENCES TO dbrole_offline
- GRANT EXECUTE    ON FUNCTIONS TO dbrole_offline
- GRANT INSERT     ON TABLES    TO dbrole_readwrite
- GRANT UPDATE     ON TABLES    TO dbrole_readwrite
- GRANT DELETE     ON TABLES    TO dbrole_readwrite
- GRANT USAGE      ON SEQUENCES TO dbrole_readwrite
- GRANT UPDATE     ON SEQUENCES TO dbrole_readwrite
- GRANT TRUNCATE   ON TABLES    TO dbrole_admin
- GRANT REFERENCES ON TABLES    TO dbrole_admin
- GRANT TRIGGER    ON TABLES    TO dbrole_admin
- GRANT CREATE     ON SCHEMAS   TO dbrole_admin

Newly created objects will have corresponding privileges when it is created by admin users

The \ddp+ may looks like:

Type Access privileges
function =X
dbrole_readonly=X
dbrole_offline=X
dbrole_admin=X
schema dbrole_readonly=U
dbrole_offline=U
dbrole_admin=UC
sequence dbrole_readonly=r
dbrole_offline=r
dbrole_readwrite=wU
dbrole_admin=rwU
table dbrole_readonly=r
dbrole_offline=r
dbrole_readwrite=awd
dbrole_admin=arwdDxt

Default Privilege

ALTER DEFAULT PRIVILEGES allows you to set the privileges that will be applied to objects created in the future. It does not affect privileges assigned to already-existing objects, and objects created by non-admin users.

Pigsty will use the following default privileges:

{% for priv in pg_default_privileges %}
ALTER DEFAULT PRIVILEGES FOR ROLE {{ pg_dbsu }} {{ priv }};
{% endfor %}

{% for priv in pg_default_privileges %}
ALTER DEFAULT PRIVILEGES FOR ROLE {{ pg_admin_username }} {{ priv }};
{% endfor %}

-- for additional business admin, they can SET ROLE to dbrole_admin
{% for priv in pg_default_privileges %}
ALTER DEFAULT PRIVILEGES FOR ROLE "dbrole_admin" {{ priv }};
{% endfor %}

Which will be rendered in pg-init-template.sql alone with ALTER DEFAULT PRIVILEGES statement for admin users.

These SQL commands will be executed on postgres & template1 during cluster bootstrap, and newly created databases will inherit it from template1 by default.

That is to say, to maintain the correct object privilege, you have to run DDL with admin users, which could be:

  1. {{ pg_dbsu }}, postgres by default
  2. {{ pg_admin_username }}, dbuser_dba by default
  3. Business admin user granted with dbrole_admin

It’s wise to use postgres as the global object owner to perform DDL changes. If you wish to create objects with business admin user, YOU MUST USE SET ROLE dbrole_admin before running that DDL to maintain the correct privileges.

You can also ALTER DEFAULT PRIVILEGE FOR ROLE <some_biz_admin> XXX to grant default privilege to business admin user, too.


Database Privilege

Database privilege is covered by database definition.

There are 3 database level privileges: CONNECT, CREATE, TEMP, and a special ‘privilege’: OWNERSHIP.

- name: meta         # required, `name` is the only mandatory field of a database definition
  owner: postgres    # optional, specify a database owner, {{ pg_dbsu }} by default
  allowconn: true    # optional, allow connection, true by default. false will disable connect at all
  revokeconn: false  # optional, revoke public connection privilege. false by default. (leave connect with grant option to owner)
  • If owner exists, it will be used as the database owner instead of default {{ pg_dbsu }}
  • If revokeconn is false, all users have the CONNECT privilege of the database, this is the default behavior.
  • If revokeconn is set to true explicitly:
  • CONNECT privilege of the database will be revoked from PUBLIC
  • CONNECT privilege will be granted to {{ pg_replication_username }}, {{ pg_monitor_username }} and {{ pg_admin_username }}
  • CONNECT privilege will be granted to the database owner with GRANT OPTION

revokeconn flag can be used for database access isolation, you can create different business users as the owners for each database and set the revokeconn option for all of them.


Create Privilege

Pigsty revokes the CREATE privilege on database from PUBLIC by default, for security consideration. And this is the default behavior since PostgreSQL 15.

The database owner has the full ability to adjust these privileges as they see fit.

13 - Dashboard

check visualized information

Grafana Dashboards for PostgreSQL clusters: Demo & Gallery.

pigsty-dashboard.jpg

There are 26 default grafana dashboards about PostgreSQL and categorized into 4 levels. and categorized into PGSQL, PGCAT & PGLOG by datasource.

Overview

  • pgsql-overview : The main dashboard for PGSQL module
  • pgsql-alert : Global PGSQL key metrics and alerting events
  • pgsql-shard : Overview of a horizontal sharded PGSQL cluster, e.g. citus / gpsql cluster

Cluster

  • pgsql-cluster: The main dashboard for a PGSQL cluster
  • pgrds-cluster: The PGSQL Cluster dashboard for RDS, focus on all postgres metrics only.
  • pgsql-activity: Cares about the Session/Load/QPS/TPS/Locks of a PGSQL cluster
  • pgsql-replication: Cares about PGSQL cluster replication, slots, and pub/sub.
  • pgsql-service: Cares about PGSQL cluster services, proxies, routes, and load balancers.
  • pgsql-databases: Cares about database CRUD, slow queries, and table statistics cross all instances.
  • pgsql-patroni: Cares about cluster HA agent: patroni status.
  • pgsql-pitr: Cares about context of cluster status during PITR procedure

Instance

  • pgsql-instance: The main dashboard for a single PGSQL instance
  • pgrds-instance: The PGSQL Instance dashboard for RDS, focus on all postgres metrics only.
  • pgcat-instance: Instance information from database catalog directly
  • pgsql-persist: Metrics about persistence: WAL, XID, Checkpoint, Archive, IO
  • pgsql-proxy: Metrics about haproxy the service provider
  • pgsql-queries: Overview of all queries in a single instance
  • pgsql-session: Metrics about sessions and active/idle time in a single instance
  • pgsql-xacts: Metrics about transactions, locks, queries, etc…
  • pgsql-exporter: Postgres & Pgbouncer exporter self monitoring metrics

Database

  • pgsql-database: The main dashboard for a single PGSQL database
  • pgcat-database: Database information from database catalog directly
  • pgsql-tables : Table/Index access metrics inside a single database
  • pgsql-table: Detailed information (QPS/RT/Index/Seq…) about a single table
  • pgcat-table: Detailed information (Stats/Bloat/…) about a single table from database catalog directly
  • pgsql-query: Detailed information (QPS/RT) about a single query
  • pgcat-query: Detailed information (SQL/Stats) about a single query from database catalog directly

Overview

PGSQL Overview : The main dashboard for PGSQL module

PGSQL Alert : Global PGSQL key metrics and alerting events

PGSQL Shard : Overview of a horizontal sharded PGSQL cluster, e.g. CITUS / GPSQL cluster


Cluster

PGSQL Cluster: The main dashboard for a PGSQL cluster

PGRDS Cluster: The PGSQL Cluster dashboard for RDS, focus on all postgres metrics only.

PGSQL Service: Cares about PGSQL cluster services, proxies, routes, and load balancers.

PGSQL Activity: Cares about the Session/Load/QPS/TPS/Locks of a PGSQL cluster

PGSQL Replication: Cares about PGSQL cluster replication, slots, and pub/sub.

PGSQL Databases: Cares about database CRUD, slow queries, and table statistics cross all instances.

PGSQL Patroni: Cares about cluster HA agent: patroni status.

PGSQL PITR: Cares about context of cluster status during PITR procedure


Instance

PGSQL Instance: The main dashboard for a single PGSQL instance

PGRDS Instance: The PGSQL Instance dashboard for RDS, focus on all postgres metrics only.

PGSQL Proxy: Metrics about haproxy the service provider

PGSQL Pgbouncer: Metrics about one single pgbouncer connection pool instance

PGSQL Persist: Metrics about persistence: WAL, XID, Checkpoint, Archive, IO

PGSQL Xacts: Metrics about transactions, locks, queries, etc…

PGSQL Session: Metrics about sessions and active/idle time in a single instance

PGSQL Exporter: Postgres & Pgbouncer exporter self monitoring metrics


Database

PGSQL Database: The main dashboard for a single PGSQL database

PGSQL Tables : Table/Index access metrics inside a single database

PGSQL Table: Detailed information (QPS/RT/Index/Seq…) about a single table

PGSQL Query: Detailed information (QPS/RT) about a single query


PGCAT

PGCAT Instance: Instance information from database catalog directly

PGCAT Database: Database information from database catalog directly

PGCAT Schema: Detailed information about one single schema from database catalog directly

PGCAT Table: Detailed information about one single table from database catalog directly

PGCAT Query: Detailed information about one single type of query from database catalog directly

PGCAT Locks: Detailed information about live locks & activity from database catalog directly


PGLOG

PGLOG Overview: Overview of csv log sample in pigsty meta database

PGLOG Overview: Detail of one single session of csv log sample in pigsty meta database

14 - Migration

zero-downtime blue-green deployment

Pigsty has a built-in playbook pgsql-migration.yml to perform online database migration based on logical replication.

With proper automation, the downtime could be minimized to several seconds. But beware that logical replication requires PostgreSQL 10+ to work. You can still use the facility here and use a pg_dump | psql instead of logical replication.


Define Migration Task

You have to create a migration task definition file to use this playbook.

Check files/migration/pg-meta.yml for example.

It will try to migrate the pg-meta.meta to pg-test.test.

pg-meta-1	10.10.10.10  --> pg-test-1	10.10.10.11 (10.10.10.12,10.10.10.13)

You have to tell pigsty where is the source cluster and destination cluster. The database to be migrated, and the primary IP address.

You should have superuser privileges on both sides to proceed

You can overwrite the superuser connection to the source cluster with src_pg, and logical replication connection string with sub_conn, Otherwise, pigsty default admin & replicator credentials will be used.

---
#-----------------------------------------------------------------
# PG_MIGRATION
#-----------------------------------------------------------------
context_dir: ~/migration           # migration manuals & scripts
#-----------------------------------------------------------------
# SRC Cluster (The OLD Cluster)
#-----------------------------------------------------------------
src_cls: pg-meta      # src cluster name         <REQUIRED>
src_db: meta          # src database name        <REQUIRED>
src_ip: 10.10.10.10   # src cluster primary ip   <REQUIRED>
#src_pg: ''            # if defined, use this as src dbsu pgurl instead of:
#                      # postgres://{{ pg_admin_username }}@{{ src_ip }}/{{ src_db }}
#                      # e.g. 'postgres://dbuser_dba:[email protected]:5432/meta'
#sub_conn: ''          # if defined, use this as subscription connstr instead of:
#                      # host={{ src_ip }} dbname={{ src_db }} user={{ pg_replication_username }}'
#                      # e.g. 'host=10.10.10.10 dbname=meta user=replicator password=DBUser.Replicator'
#-----------------------------------------------------------------
# DST Cluster (The New Cluster)
#-----------------------------------------------------------------
dst_cls: pg-test      # dst cluster name         <REQUIRED>
dst_db: test          # dst database name        <REQUIRED>
dst_ip: 10.10.10.11   # dst cluster primary ip   <REQUIRED>
#dst_pg: ''            # if defined, use this as dst dbsu pgurl instead of:
#                      # postgres://{{ pg_admin_username }}@{{ dst_ip }}/{{ dst_db }}
#                      # e.g. 'postgres://dbuser_dba:[email protected]:5432/test'
#-----------------------------------------------------------------
# PGSQL
#-----------------------------------------------------------------
pg_dbsu: postgres
pg_replication_username: replicator
pg_replication_password: DBUser.Replicator
pg_admin_username: dbuser_dba
pg_admin_password: DBUser.DBA
pg_monitor_username: dbuser_monitor
pg_monitor_password: DBUser.Monitor
#-----------------------------------------------------------------
...

Generate Plan

The playbook does not migrate src to dst, but it will generate everything your need to do so.

After the execution, you will find migration context dir under ~/migration/pg-meta.meta by default

Following the README.md and executing these scripts one by one, you will do the trick!

# this script will setup migration context with env vars
. ~/migration/pg-meta.meta/activate

# these scripts are used for check src cluster status
# and help generating new cluster definition in pigsty
./check-user     # check src users
./check-db       # check src databases
./check-hba      # check src hba rules
./check-repl     # check src replica identities
./check-misc     # check src special objects

# these scripts are used for building logical replication
# between existing src cluster and pigsty managed dst cluster
# schema, data will be synced in realtime, except for sequences
./copy-schema    # copy schema to dest
./create-pub     # create publication on src
./create-sub     # create subscription on dst
./copy-progress  # print logical replication progress
./copy-diff      # quick src & dst diff by counting tables

# these scripts will run in an online migration, which will
# stop src cluster, copy sequence numbers (which is not synced with logical replication)
# you have to reroute you app traffic according to your access method (dns,vip,haproxy,pgbouncer,etc...)
# then perform cleanup to drop subscription and publication
./copy-seq [n]   # sync sequence numbers, if n is given, an additional shift will applied
#./disable-src   # restrict src cluster access to admin node & new cluster (YOUR IMPLEMENTATION)
#./re-routing    # ROUTING APPLICATION TRAFFIC FROM SRC TO DST!            (YOUR IMPLEMENTATION)
./drop-sub       # drop subscription on dst after migration
./drop-pub       # drop publication on src after migration

Caveats

You can use ./copy-seq 1000 to advance all sequences by a number (e.g. 1000) after syncing sequences. Which may prevent potential serial primary key conflict in new clusters.

You have to implement your own ./re-routing script to route your application traffic from src to dst. Since we don’t know how your traffic is routed (e.g dns, VIP, haproxy, or pgbouncer). Of course, you can always do that by hand…

You have to implement your own ./disable-src script to restrict the src cluster. You can do that by changing HBA rules & reload (recommended), or just shutting down postgres, pgbouncer, or haproxy…

15 - Backup

Backup and point-in-time recovery

Pigsty uses pgBackRest to manage PostgreSQL backups, it may be the most powerful open-source backup tools in the ecosystem. With incremental / parallel backup & restore, encryption, MinIO / S3 support, and many other features. Pigsty has pre-configured it for every PGSQL cluster by default.

Policy
    Backup scripts, scheduling, pgbackrest, repo and admin
Admin
    Backup policy, disk planning, recovery window trade-off
Restore
    Restore to specific time point with playbook
Example
    Sandbox example: Perform recovery with bare hands
NO WRANTTY

Pigsty try its best to provide a reliable PITR solution, but we do not take any responsibility for the data loss caused by the PITR operation, use it at your own risk. For professional support, consider our pro service.


Quick Start

Step 1

    [Backup Policy](/docs/pgsql/backup/mechanism): Schedule Base Backups with Crontab

Step 2

    [WAL Archiving](/docs/pgsql/backup/policy): Continuously record write activities

Step 3

    [Restore & Recovery](/docs/pgsql/backup/restore): Recover from backup and wal archive
Full backup everyday 1am
node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ]
restore to a timepoint
./pgsql-pitr.yml -e '{"pg_pitr": { "time": "2025-07-13 10:00:00+00" }}'

15.1 - Mechanism

Backup script, schedule, repository, and infrastructure

Backups can be invoked by built-in scripts, scheduled with node crontab, managed by pgbackrest, and stored in backup repo, which could be local disk filesystem or MinIO / S3, with different retention policies.


Script

You can create a backup with pgbackrest command with pg_dbsu user (postgres by default):

pgbackrest --stanza=pg-meta --type=full backup   # create a full backup for cluster pg-meta
$ pgbackrest --stanza=pg-meta --type=full backup
2025-07-15 01:36:57.007 P00   INFO: backup command begin 2.54.2: --annotation=pg_cluster=pg-meta --compress-type=lz4 --delta --exec-id=88380-4b22e767 --expire-auto --log-level-console=info --log-level-file=info --log-path=/pg/log/pgbackrest --pg1-path=/pg/data --pg1-port=5432 --repo1-block --repo1-bundle --repo1-bundle-limit=20MiB --repo1-bundle-size=128MiB --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/pgbackrest --repo1-retention-full=14 --repo1-retention-full-type=time --repo1-s3-bucket=pgsql --repo1-s3-endpoint=sss.pigsty --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-s3-uri-style=path --repo1-storage-ca-file=/etc/pki/ca.crt --repo1-storage-port=9000 --repo1-type=s3 --stanza=pg-meta --start-fast --type=full
2025-07-15 01:36:57.030 P00   INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
2025-07-15 01:36:57.105 P00   INFO: backup start archive = 000000010000000000000006, lsn = 0/6000028
2025-07-15 01:36:57.105 P00   INFO: check archive for prior segment 000000010000000000000005
2025-07-15 01:36:58.403 P00   INFO: execute non-exclusive backup stop and wait for all WAL segments to archive
2025-07-15 01:36:58.421 P00   INFO: backup stop archive = 000000010000000000000006, lsn = 0/6000120
2025-07-15 01:36:58.424 P00   INFO: check archive for segment(s) 000000010000000000000006:000000010000000000000006
2025-07-15 01:36:58.540 P00   INFO: new backup label = 20250715-013657F
2025-07-15 01:36:58.588 P00   INFO: full backup size = 44.5MB, file total = 1437
2025-07-15 01:36:58.589 P00   INFO: backup command end: completed successfully (1584ms)
2025-07-15 01:36:58.589 P00   INFO: expire command begin 2.54.2: --exec-id=88380-4b22e767 --log-level-console=info --log-level-file=info --log-path=/pg/log/pgbackrest --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/pgbackrest --repo1-retention-full=14 --repo1-retention-full-type=time --repo1-s3-bucket=pgsql --repo1-s3-endpoint=sss.pigsty --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-s3-uri-style=path --repo1-storage-ca-file=/etc/pki/ca.crt --repo1-storage-port=9000 --repo1-type=s3 --stanza=pg-meta
2025-07-15 01:36:58.593 P00   INFO: repo1: time-based archive retention not met - archive logs will not be expired
2025-07-15 01:36:58.593 P00   INFO: expire command end: completed successfully (4ms)
$ pgbackrest --stanza=pg-meta --type=diff backup
2025-07-15 01:37:24.952 P00   INFO: backup command begin 2.54.2: --annotation=pg_cluster=pg-meta --compress-type=lz4 --delta --exec-id=88431-1b8ca3e0 --expire-auto --log-level-console=info --log-level-file=info --log-path=/pg/log/pgbackrest --pg1-path=/pg/data --pg1-port=5432 --repo1-block --repo1-bundle --repo1-bundle-limit=20MiB --repo1-bundle-size=128MiB --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/pgbackrest --repo1-retention-full=14 --repo1-retention-full-type=time --repo1-s3-bucket=pgsql --repo1-s3-endpoint=sss.pigsty --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-s3-uri-style=path --repo1-storage-ca-file=/etc/pki/ca.crt --repo1-storage-port=9000 --repo1-type=s3 --stanza=pg-meta --start-fast --type=diff
2025-07-15 01:37:24.985 P00   INFO: last backup label = 20250715-013657F, version = 2.54.2
2025-07-15 01:37:24.985 P00   INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
2025-07-15 01:37:25.045 P00   INFO: backup start archive = 000000010000000000000008, lsn = 0/8000028
2025-07-15 01:37:25.045 P00   INFO: check archive for prior segment 000000010000000000000007
2025-07-15 01:37:26.204 P00   INFO: execute non-exclusive backup stop and wait for all WAL segments to archive
2025-07-15 01:37:26.220 P00   INFO: backup stop archive = 000000010000000000000008, lsn = 0/8000158
2025-07-15 01:37:26.223 P00   INFO: check archive for segment(s) 000000010000000000000008:000000010000000000000008
2025-07-15 01:37:26.337 P00   INFO: new backup label = 20250715-013657F_20250715-013724D
2025-07-15 01:37:26.381 P00   INFO: diff backup size = 424.3KB, file total = 1437
2025-07-15 01:37:26.381 P00   INFO: backup command end: completed successfully (1431ms)
2025-07-15 01:37:26.381 P00   INFO: expire command begin 2.54.2: --exec-id=88431-1b8ca3e0 --log-level-console=info --log-level-file=info --log-path=/pg/log/pgbackrest --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/pgbackrest --repo1-retention-full=14 --repo1-retention-full-type=time --repo1-s3-bucket=pgsql --repo1-s3-endpoint=sss.pigsty --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-s3-uri-style=path --repo1-storage-ca-file=/etc/pki/ca.crt --repo1-storage-port=9000 --repo1-type=s3 --stanza=pg-meta
2025-07-15 01:37:26.386 P00   INFO: repo1: time-based archive retention not met - archive logs will not be expired
2025-07-15 01:37:26.386 P00   INFO: expire command end: completed successfully (5ms)
$ pgbackrest --stanza=pg-meta --type=incr backup
2025-07-15 01:37:30.305 P00   INFO: backup command begin 2.54.2: --annotation=pg_cluster=pg-meta --compress-type=lz4 --delta --exec-id=88449-eba235f7 --expire-auto --log-level-console=info --log-level-file=info --log-path=/pg/log/pgbackrest --pg1-path=/pg/data --pg1-port=5432 --repo1-block --repo1-bundle --repo1-bundle-limit=20MiB --repo1-bundle-size=128MiB --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/pgbackrest --repo1-retention-full=14 --repo1-retention-full-type=time --repo1-s3-bucket=pgsql --repo1-s3-endpoint=sss.pigsty --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-s3-uri-style=path --repo1-storage-ca-file=/etc/pki/ca.crt --repo1-storage-port=9000 --repo1-type=s3 --stanza=pg-meta --start-fast --type=incr
2025-07-15 01:37:30.337 P00   INFO: last backup label = 20250715-013657F_20250715-013724D, version = 2.54.2
2025-07-15 01:37:30.337 P00   INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
2025-07-15 01:37:30.383 P00   INFO: backup start archive = 000000010000000000000009, lsn = 0/9000028
2025-07-15 01:37:30.383 P00   INFO: check archive for segment 000000010000000000000009
2025-07-15 01:37:31.191 P00   INFO: execute non-exclusive backup stop and wait for all WAL segments to archive
2025-07-15 01:37:31.230 P00   INFO: backup stop archive = 00000001000000000000000A, lsn = 0/A000050
2025-07-15 01:37:31.232 P00   INFO: check archive for segment(s) 000000010000000000000009:00000001000000000000000A
2025-07-15 01:37:31.356 P00   INFO: new backup label = 20250715-013657F_20250715-013730I
2025-07-15 01:37:31.403 P00   INFO: incr backup size = 8.3KB, file total = 1437
2025-07-15 01:37:31.403 P00   INFO: backup command end: completed successfully (1099ms)
2025-07-15 01:37:31.403 P00   INFO: expire command begin 2.54.2: --exec-id=88449-eba235f7 --log-level-console=info --log-level-file=info --log-path=/pg/log/pgbackrest --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/pgbackrest --repo1-retention-full=14 --repo1-retention-full-type=time --repo1-s3-bucket=pgsql --repo1-s3-endpoint=sss.pigsty --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-s3-uri-style=path --repo1-storage-ca-file=/etc/pki/ca.crt --repo1-storage-port=9000 --repo1-type=s3 --stanza=pg-meta
2025-07-15 01:37:31.409 P00   INFO: repo1: time-based archive retention not met - archive logs will not be expired
2025-07-15 01:37:31.409 P00   INFO: expire command end: completed successfully (6ms)
$ pgbackrest --stanza=pg-meta info
stanza: pg-meta
    status: ok
    cipher: aes-256-cbc

    db (current)
        wal archive min/max (17): 000000010000000000000001/00000001000000000000000A

        full backup: 20250715-013441F
            timestamp start/stop: 2025-07-15 01:34:41+00 / 2025-07-15 01:34:43+00
            wal start/stop: 000000010000000000000004 / 000000010000000000000004
            database size: 43.9MB, database backup size: 43.9MB
            repo1: backup size: 8.3MB

        full backup: 20250715-013657F
            timestamp start/stop: 2025-07-15 01:36:57+00 / 2025-07-15 01:36:58+00
            wal start/stop: 000000010000000000000006 / 000000010000000000000006
            database size: 44.5MB, database backup size: 44.5MB
            repo1: backup size: 8.7MB

        diff backup: 20250715-013657F_20250715-013724D
            timestamp start/stop: 2025-07-15 01:37:24+00 / 2025-07-15 01:37:26+00
            wal start/stop: 000000010000000000000008 / 000000010000000000000008
            database size: 44.5MB, database backup size: 424.3KB
            repo1: backup size: 94KB
            backup reference total: 1 full

        incr backup: 20250715-013657F_20250715-013730I
            timestamp start/stop: 2025-07-15 01:37:30+00 / 2025-07-15 01:37:31+00
            wal start/stop: 000000010000000000000009 / 00000001000000000000000A
            database size: 44.5MB, database backup size: 8.3KB
            repo1: backup size: 504B
            backup reference total: 1 full, 1 diff

The stanza here is the database cluster name: pg_cluster, which is pg-meta for the default setup.

Pigsty has an alias pb and wrapper script pg-backup that fills the current cluster name as stanza:

alias
function pb() {
    local stanza=$(grep -o '\[[^][]*]' /etc/pgbackrest/pgbackrest.conf | head -n1 | sed 's/.*\[\([^]]*\)].*/\1/')
    pgbackrest --stanza=$stanza $@
}
pb ...    # pgbackrest --stanza=pg-meta ...
pb info   # pgbackrest --stanza=pg-meta info
pb backup # pgbackrest --stanza=pg-meta backup
script
pg-backup full   # take an full backup         = pgbackrest --stanza=pg-meta --type=incr backup
pg-backup incr   # take an incremental backup  = pgbackrest --stanza=pg-meta --type=incr backup
pg-backup diff   # take an differential backup = pgbackrest --stanza=pg-meta --type=incr backup

Crontab

Pigsty is leveraging Linux’s crontab to schedule backups. You can define your backup policies with it

For example, most one-node config template will have the following node_crontab for backup.

Full backup everyday 1am
node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ]

You can design more sophisticated backup policies with crontab and pg-backup script, such as:

Full backup on Monday, incremental backup during weekdays
node_crontab:  # make a full backup on monday 1am, and an incremental backup during weekdays
  - '00 01 * * 1 postgres /pg/bin/pg-backup full'
  - '00 01 * * 2,3,4,5,6,7 postgres /pg/bin/pg-backup'

To apply crontab change, use the node.yml to update the crontab on all nodes.

apply crontab
./node.yml -t node_crontab -l pg-meta    # apply crontab change to the pg-meta group

pgbackrest

Here’s pigsty’s setup details for pgbackrest:

  • The pgbackrest backup tool is enabled and configured by default (pgbackrest_enabled)
  • Installed in the pg_install task in the pgsql.yml playbook, defined in pg_packages
  • configured in the pg_backup task in the pgsql.yml playbook, PARAM: PG_BACKUP
  • init backup repo in the pgbackrest_init task, fails if repo exists! (errors can be ignored)
  • Create initial backup in the pgbackrest_backup task, controlled by pgbackrest_init_backup

FHS

  • bin: /usr/bin/pgbackrest, from the PGDG’s pgbackrest package, in the group alias pgsql-common.
  • conf: /etc/pgbackrest, the main config is /etc/pgbackrest/pgbackrest.conf.
  • logs: /pg/log/pgbackrest/*, controlled by pgbackrest_log_dir
  • tmp: /pg/spool is used as the temp spool directory for pgbackrest
  • data: /pg/backup is used, if the default local filesystem backup repo is selected.

Moreover, during the PITR Recovery process, Pigsty will create a temp /pg/conf/pitr.conf pgbackrest config file. And write postgres recovery log to the /pg/tmp/recovery.log file.

Monitoring

There is a pgbackrest_exporter service running on (pgbackrest_exporter_port: 9854) to export the pgbackrest metrics. You can customize it by pgbackrest_exporter_options and disable it with setting pgbackrest_exporter_enabled to false.

Initial Backup

When a postgres cluster is created, pigsty will create an initial backup automatically. It’s a tiny backup since the new cluster is almost empty. It will leave a marker file /etc/pgbackrest/initial.done to avoid creating the initial backup again. Set the pgbackrest_init_backup to false if you don’t want it.


Administration

Enable Backup

If you database cluster is created with pgbackrest_enable set to true, the backup will be enabled automatically.

If it created with the false value, you can enable the pgbackrest component with:

./pgsql.yml -t pg_backup    # run the pgbackrest subtask

Remove Backup

Pigsty will remove pgbackrest backup stanza when removing the primary instance (pg_role = primary).

./pgsql-rm.yml
./pgsql-rm.yml -e pg_rm_backup=false   # leave backup intact
./pgsql-rm.yml -t pg_backup            # only remove backup

Use the pg_backup subtask to remove the backup only, and use the pg_rm_backup arg to keep backups.

If your backup repo is locked, (e.g., S3 / MinIO has a lock option), this operation will fail.

Backup Removal

Removing backup may lead to permanent data loss, it’s a dangerous operation, do with extreme caution.

List Backup

This command will list all backups in the pgbackrest repository (shared by all clusters)

pgbackrest info

Manual Backup

Pigsty has a built-in script /pg/bin/pg-backup which wraps the pgbackrest backup command.

pg-backup        # take an incremental backup
pg-backup full   # take an full backup
pg-backup incr   # take an incremental backup
pg-backup diff   # take an differential backup

Base Backup

Pigsty has an alternative backup script /pg/bin/pg-basebackup which does not rely on pgbackrest, and gives you a physical copy of the database cluster. The default backup dir is /pg/backup.

NAME
  pg-basebackup  -- make base backup from PostgreSQL instance

SYNOPSIS
  pg-basebackup -sdfeukr
  pg-basebackup --src postgres:/// --dst . --file backup.tar.lz4

DESCRIPTION
-s, --src, --url     Backup source URL, optional, "postgres:///" by default, if password is required, it should be given in url, ENV or .pgpass
-d, --dst, --dir     Where to put backup files, "/pg/backup" by default
-f, --file           Overwrite default backup filename, "backup_${tag}_${date}.tar.lz4"
-r, --remove         .lz4 Files mtime before n minutes ago will be removed, default is 1200 (20hour)
-t, --tag            Backup file tag, if not set, target cluster_name or local ip address will be used. Also used as part of DEFAULT filename
-k, --key            Encryption key when --encrypt is specified, default key is ${tag}
-u, --upload         Upload backup files to cloud storage, (need your own implementation)
-e, --encryption     Encrypt with RC4 using OpenSSL, if not key is specified, tag is used as key
-h, --help           Print this message
postgres@pg-meta-1:~$ pg-basebackup
[2025-07-13 06:16:05][INFO] ================================================================
[2025-07-13 06:16:05][INFO] [INIT] pg-basebackup begin, checking parameters
[2025-07-13 06:16:05][DEBUG] [INIT] #====== BINARY
[2025-07-13 06:16:05][DEBUG] [INIT] pg_basebackup     :   /usr/pgsql/bin/pg_basebackup
[2025-07-13 06:16:05][DEBUG] [INIT] openssl           :   /usr/bin/openssl
[2025-07-13 06:16:05][DEBUG] [INIT] #====== PARAMETER
[2025-07-13 06:16:05][DEBUG] [INIT] filename  (-f)    :   backup_pg-meta_20250713.tar.lz4
[2025-07-13 06:16:05][DEBUG] [INIT] src       (-s)    :   postgres:///
[2025-07-13 06:16:05][DEBUG] [INIT] dst       (-d)    :   /pg/backup
[2025-07-13 06:16:05][DEBUG] [INIT] tag       (-t)    :   pg-meta
[2025-07-13 06:16:05][DEBUG] [INIT] key       (-k)    :   pg-meta
[2025-07-13 06:16:05][DEBUG] [INIT] encrypt   (-e)    :   false
[2025-07-13 06:16:05][DEBUG] [INIT] upload    (-u)    :   false
[2025-07-13 06:16:05][DEBUG] [INIT] remove    (-r)    :   -mmin +1200
[2025-07-13 06:16:05][INFO] [LOCK] acquire lock @ /tmp/backup.lock
[2025-07-13 06:16:05][INFO] [LOCK] lock acquired success on /tmp/backup.lock, pid=107417
[2025-07-13 06:16:05][INFO] [BKUP] backup begin, from postgres:/// to /pg/backup/backup_pg-meta_20250713.tar.lz4
[2025-07-13 06:16:05][INFO] [BKUP] backup in normal mode
pg_basebackup: initiating base backup, waiting for checkpoint to complete

pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/7000028 on timeline 1
pg_basebackup: write-ahead log end point: 0/7000FD8
pg_basebackup: syncing data to disk ...
pg_basebackup: base backup completed
[2025-07-13 06:16:06][INFO] [BKUP] backup complete!
[2025-07-13 06:16:06][INFO] [RMBK] remove local obsolete backup: 1200
[2025-07-13 06:16:06][INFO] [BKUP] find obsolete backups: find /pg/backup/ -maxdepth 1 -type f -mmin +1200 -name 'backup*.lz4'
[2025-07-13 06:16:06][WARN] [BKUP] remove obsolete backups:
[2025-07-13 06:16:06][INFO] [RMBK] remove old backup complete
[2025-07-13 06:16:06][INFO] [LOCK] release lock @ /tmp/backup.lock
[2025-07-13 06:16:06][INFO] [DONE] backup procedure complete!
[2025-07-13 06:16:06][INFO] ================================================================

Backup are compressed with lz4, You can unzip and extract the tarball with the following command:

mkdir -p /tmp/data   # extract backup to this directory
cat /pg/backup/backup_pg-meta_20250713.tar.lz4 | unlz4 -d -c | tar -xC /tmp/data

Logical Backup

You can also use the pg_dump command to perform a logical backup.

Logical backups cannot be used for PITR (Point In Time Recovery), but they are useful for migrating data between different major versions, or implement flexible data export logic.

Bootstrap from Repo

Now let’s say you have an existing cluster pg-meta, and want to FORK it as pg-meta2:

You’ll need to create the new pg-meta2 cluster fork, then run pitr on it.

15.2 - Repository

Backup storage repository for PostgreSQL

You can to configure WHERE to store the backups by specifying the pgbackrest_repo parameter. You can define multiple repo there, and Pigsty will pick it according to the value of pgbackrest_method.

Default Repo

By default, Pigsty has two default backup repo definition: the local and minio backup repo.

  • local: The default, use the local /pg/backup dir (Softlink point to pg_fs_backup: /data/backups)
  • minio: Use the SNSD 1-node MinIO cluster (Supported by pigsty, but not enabled by default)
pgbackrest_method: local          # choose the backup repo method, `local` or `minio` or any other user defined repo
pgbackrest_repo:                  # pgbackrest repo: https://pgbackrest.org/configuration.html#section-repository
  local:                          # default pgbackrest repo with local posix fs
    path: /pg/backup              # local backup directory, `/pg/backup` by default
    retention_full_type: count    # retention full backups by count
    retention_full: 2             # keep 2, at most 3 full backups when using local fs repo
  minio:                          # optional minio repo for pgbackrest
    type: s3                      # minio is s3-compatible, so s3 is used
    s3_endpoint: sss.pigsty       # minio endpoint domain name, `sss.pigsty` by default
    s3_region: us-east-1          # minio region, us-east-1 by default, useless for minio
    s3_bucket: pgsql              # minio bucket name, `pgsql` by default
    s3_key: pgbackrest            # minio user access key for pgbackrest
    s3_key_secret: S3User.Backup  # minio user secret key for pgbackrest
    s3_uri_style: path            # use path style uri for minio rather than host style
    path: /pgbackrest             # minio backup path, default is `/pgbackrest`
    storage_port: 9000            # minio port, 9000 by default
    storage_ca_file: /etc/pki/ca.crt  # minio ca file path, `/etc/pki/ca.crt` by default
    block: y                      # Enable block incremental backup
    bundle: y                     # bundle small files into a single file
    bundle_limit: 20MiB           # Limit for file bundles, 20MiB for object storage
    bundle_size: 128MiB           # Target size for file bundles, 128MiB for object storage
    cipher_type: aes-256-cbc      # enable AES encryption for remote backup repo
    cipher_pass: pgBackRest       # AES encryption password, default is 'pgBackRest'
    retention_full_type: time     # retention full backup by time on minio repo
    retention_full: 14            # keep full backup for the last 14 days

Repo Retention

If you take backups every day without deleting them, the backup repo will grow larger and larger and blow your disk space. You’ll need to define a retention policy to only keep a limited number of backups.

The default backup policy is defined in the pgbackrest_repo parameter, change them on demand.

  • local: keep last 2 full backups, at most 3 during backup
  • minio: keep all full backups in the last 14 days

Space Planning

Object storage provides virtually unlimited storage capacity, so you don’t need to worry about the disk space. You can optimize space usage with a hybrid full & diff backup policy.

For local disk backup repo, pigsty recommends using a retention policy of keeping the last 2 full backups, which means keep the two most-recent full backups on disk (a third copy may exist while a new backup is running).

This gives you a guaranteed recovery window of at least last 24 hours. Check backup policy for details.


Repo Alternative

You can also use other services as backup repo, check pgbackrest documentation for details:


Repo Versioning

You can even specify a repo target time to get a snapshot of object storage.

You can enable MinIO versioning by adding versioning flag to the minio_buckets:

minio_buckets:
  - { name: pgsql ,versioning: true }
  - { name: meta  ,versioning: true }
  - { name: data }

Repo Locking

Some object storage service (S3, MinIO, etc.) supports the locking, which can prevent the backup from being deleted, even by DBA themselves.

You can enable MinIO locking feature by adding lock flag to the minio_buckets:

minio_buckets:
  - { name: pgsql , lock: true }
  - { name: meta ,versioning: true  }
  - { name: data }

Use Object Storage

Object storage service provides virtually unlimited storage capacity, and provides a remote disaster tolerance for your system. If you don’t have one, Pigsty has built-in MinIO support.

MinIO

You can enable minio backup repo by uncommenting the following settings. Beware that pgbackrest only takes HTTPS / domain names, so you have to run MinIO with a domain name and HTTPS endpoint.

all:
  vars:
    pgbackrest_method: minio      # use minio as the default backup repo
  children:                       # define a one-node minio SNSD cluster
    minio: { hosts: { 10.10.10.10: { minio_seq: 1 }} ,vars: { minio_cluster: minio }}

S3

If you only have one node, the meaningful backup policy could be using a cloud vendor’s object storage service such as AWS S3, Aliyun OSS, or Google Cloud, etc… To achieve this, you can define a new repo:

pgbackrest_method: s3             # use the 'pgbackrest_repo.s3' as backup repo
pgbackrest_repo:                  # pgbackrest repo: https://pgbackrest.org/configuration.html#section-repository

  s3:                             # aliyun oss (s3 compatible) object storage service
    type: s3                      # oss is s3-compatible
    s3_endpoint: oss-cn-beijing-internal.aliyuncs.com
    s3_region: oss-cn-beijing
    s3_bucket: <your_bucket_name>
    s3_key: <your_access_key>
    s3_key_secret: <your_secret_key>
    s3_uri_style: host
    path: /pgbackrest
    bundle: y                     # bundle small files into a single file
    bundle_limit: 20MiB           # Limit for file bundles, 20MiB for object storage
    bundle_size: 128MiB           # Target size for file bundles, 128MiB for object storage
    cipher_type: aes-256-cbc      # enable AES encryption for remote backup repo
    cipher_pass: pgBackRest       # AES encryption password, default is 'pgBackRest'
    retention_full_type: time     # retention full backup by time on minio repo
    retention_full: 14            # keep full backup for last 14 days

  local:                          # default pgbackrest repo with local posix fs
    path: /pg/backup              # local backup directory, `/pg/backup` by default
    retention_full_type: count    # retention full backups by count
    retention_full: 2             # keep 2, at most 3 full backups when using local fs repo

Manage Backups

Enable Backup

If you database cluster is created with pgbackrest_enable set to true, the backup will be enabled automatically.

If it created with the false value, you can enable the pgbackrest component with:

./pgsql.yml -t pg_backup    # run the pgbackrest subtask

Remove Backup

Pigsty will remove pgbackrest backup stanza when removing the primary instance (pg_role = primary).

./pgsql-rm.yml
./pgsql-rm.yml -e pg_rm_backup=false   # leave backup intact
./pgsql-rm.yml -t pg_backup            # only remove backup

Use the pg_backup subtask to remove the backup only, and use the pg_rm_backup arg to keep backups.

If your backup repo is locked, (e.g., S3 / MinIO has a lock option), this operation will fail.

Backup Removal

Removing backup may lead to permanent data loss, it’s a dangerous operation, do with extreme caution.

List Backup

This command will list all backups in the pgbackrest repository (shared by all clusters)

pgbackrest info

Manual Backup

Pigsty has a built-in script /pg/bin/pg-backup which wraps the pgbackrest backup command.

pg-backup        # take an incremental backup
pg-backup full   # take an full backup
pg-backup incr   # take an incremental backup
pg-backup diff   # take an differential backup

Base Backup

Pigsty has an alternative backup script /pg/bin/pg-basebackup which does not rely on pgbackrest, and gives you a physical copy of the database cluster. The default backup dir is /pg/backup.

NAME
  pg-basebackup  -- make base backup from PostgreSQL instance

SYNOPSIS
  pg-basebackup -sdfeukr
  pg-basebackup --src postgres:/// --dst . --file backup.tar.lz4

DESCRIPTION
-s, --src, --url     Backup source URL, optional, "postgres:///" by default, if password is required, it should be given in url, ENV or .pgpass
-d, --dst, --dir     Where to put backup files, "/pg/backup" by default
-f, --file           Overwrite default backup filename, "backup_${tag}_${date}.tar.lz4"
-r, --remove         .lz4 Files mtime before n minutes ago will be removed, default is 1200 (20hour)
-t, --tag            Backup file tag, if not set, target cluster_name or local ip address will be used. Also used as part of DEFAULT filename
-k, --key            Encryption key when --encrypt is specified, default key is ${tag}
-u, --upload         Upload backup files to cloud storage, (need your own implementation)
-e, --encryption     Encrypt with RC4 using OpenSSL, if not key is specified, tag is used as key
-h, --help           Print this message
postgres@pg-meta-1:~$ pg-basebackup
[2025-07-13 06:16:05][INFO] ================================================================
[2025-07-13 06:16:05][INFO] [INIT] pg-basebackup begin, checking parameters
[2025-07-13 06:16:05][DEBUG] [INIT] #====== BINARY
[2025-07-13 06:16:05][DEBUG] [INIT] pg_basebackup     :   /usr/pgsql/bin/pg_basebackup
[2025-07-13 06:16:05][DEBUG] [INIT] openssl           :   /usr/bin/openssl
[2025-07-13 06:16:05][DEBUG] [INIT] #====== PARAMETER
[2025-07-13 06:16:05][DEBUG] [INIT] filename  (-f)    :   backup_pg-meta_20250713.tar.lz4
[2025-07-13 06:16:05][DEBUG] [INIT] src       (-s)    :   postgres:///
[2025-07-13 06:16:05][DEBUG] [INIT] dst       (-d)    :   /pg/backup
[2025-07-13 06:16:05][DEBUG] [INIT] tag       (-t)    :   pg-meta
[2025-07-13 06:16:05][DEBUG] [INIT] key       (-k)    :   pg-meta
[2025-07-13 06:16:05][DEBUG] [INIT] encrypt   (-e)    :   false
[2025-07-13 06:16:05][DEBUG] [INIT] upload    (-u)    :   false
[2025-07-13 06:16:05][DEBUG] [INIT] remove    (-r)    :   -mmin +1200
[2025-07-13 06:16:05][INFO] [LOCK] acquire lock @ /tmp/backup.lock
[2025-07-13 06:16:05][INFO] [LOCK] lock acquired success on /tmp/backup.lock, pid=107417
[2025-07-13 06:16:05][INFO] [BKUP] backup begin, from postgres:/// to /pg/backup/backup_pg-meta_20250713.tar.lz4
[2025-07-13 06:16:05][INFO] [BKUP] backup in normal mode
pg_basebackup: initiating base backup, waiting for checkpoint to complete

pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/7000028 on timeline 1
pg_basebackup: write-ahead log end point: 0/7000FD8
pg_basebackup: syncing data to disk ...
pg_basebackup: base backup completed
[2025-07-13 06:16:06][INFO] [BKUP] backup complete!
[2025-07-13 06:16:06][INFO] [RMBK] remove local obsolete backup: 1200
[2025-07-13 06:16:06][INFO] [BKUP] find obsolete backups: find /pg/backup/ -maxdepth 1 -type f -mmin +1200 -name 'backup*.lz4'
[2025-07-13 06:16:06][WARN] [BKUP] remove obsolete backups:
[2025-07-13 06:16:06][INFO] [RMBK] remove old backup complete
[2025-07-13 06:16:06][INFO] [LOCK] release lock @ /tmp/backup.lock
[2025-07-13 06:16:06][INFO] [DONE] backup procedure complete!
[2025-07-13 06:16:06][INFO] ================================================================

Backup are compressed with lz4, You can unzip and extract the tarball with the following command:

mkdir -p /tmp/data   # extract backup to this directory
cat /pg/backup/backup_pg-meta_20250713.tar.lz4 | unlz4 -d -c | tar -xC /tmp/data

Logical Backup

You can also use the pg_dump command to perform a logical backup.

Logical backups cannot be used for PITR (Point In Time Recovery), but they are useful for migrating data between different major versions, or implement flexible data export logic.

Bootstrap from Repo

Now let’s say you have an existing cluster pg-meta, and want to FORK it as pg-meta2:

You’ll need to create the new pg-meta2 cluster fork, then run pitr on it.

15.3 - Policy

Design backup policy according to your needs.
  • WHEN: Backup Policy
  • WHERE: Backup Repo
  • HOW: Backup Method

WHEN

The first problem is WHEN to backup your database — Trade off between backup frequency and recovery time. Since you’ll need to replay the WAL logs to your recovery target since the last previous backup, the more frequent you backup, the less WAL logs you’ll need to replay, and the faster your recovery will be.

Everyday Full Backup

For a production database, it is recommended to start with the simplest everyday full backup policy. Where is the default backup policy in pigsty, implemented with crontab.

Full backup everyday 1am
node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ]
pgbackrest_method: local          # choose the backup repo method, `local` or `minio` or any other user defined repo
pgbackrest_repo:                  # pgbackrest repo: https://pgbackrest.org/configuration.html#section-repository
  local:                          # default pgbackrest repo with local posix fs
    path: /pg/backup              # local backup directory, `/pg/backup` by default
    retention_full_type: count    # retention full backups by count
    retention_full: 2             # keep 2, at most 3 full backups when using local fs repo

When using with the default local filesystem backup repo, it provides a 24~48h recovery window.

Let’s assume your database size is 100GB, and 10GB writes per day, and your backup size will be.

It will consume 2 ~ 3x of your database size, plus a 2 day’s WAL. So in practice, you may have to prepare a backup disk with at least 3 ~ 5x of your database size to use the default backup policy.

Full + Incr Backup

You can optimize backup space usage by changing these parameters.

If you are using MinIO / S3 as centralized backup repo, you can use more space than your disk limitation. Then consider the full + incr backup with 2-week retention policy:

node_crontab:  # make a full backup on monday 1am, and an incremental backup during weekdays
  - '00 01 * * 1 postgres /pg/bin/pg-backup full'
  - '00 01 * * 2,3,4,5,6,7 postgres /pg/bin/pg-backup'
pgbackrest_method: minio
pgbackrest_repo:                  # pgbackrest repo: https://pgbackrest.org/configuration.html#section-repository
  minio:                          # optional minio repo for pgbackrest
    type: s3                      # minio is s3-compatible, so s3 is used
    s3_endpoint: sss.pigsty       # minio endpoint domain name, `sss.pigsty` by default
    s3_region: us-east-1          # minio region, us-east-1 by default, useless for minio
    s3_bucket: pgsql              # minio bucket name, `pgsql` by default
    s3_key: pgbackrest            # minio user access key for pgbackrest
    s3_key_secret: S3User.Backup  # minio user secret key for pgbackrest
    s3_uri_style: path            # use path style uri for minio rather than host style
    path: /pgbackrest             # minio backup path, default is `/pgbackrest`
    storage_port: 9000            # minio port, 9000 by default
    storage_ca_file: /etc/pki/ca.crt  # minio ca file path, `/etc/pki/ca.crt` by default
    block: y                      # Enable block incremental backup
    bundle: y                     # bundle small files into a single file
    bundle_limit: 20MiB           # Limit for file bundles, 20MiB for object storage
    bundle_size: 128MiB           # Target size for file bundles, 128MiB for object storage
    cipher_type: aes-256-cbc      # enable AES encryption for remote backup repo
    cipher_pass: pgBackRest       # AES encryption password, default is 'pgBackRest'
    retention_full_type: time     # retention full backup by time on minio repo
    retention_full: 14            # keep full backup for the last 14 days

When using with the built-in minio filesystem backup repo, it provides a guaranteed 1-week pitr window.

Let’s assume your database size is 100GB, and 10GB writes per day, and your backup size will be like:


Where

By default, Pigsty has two default backup repo definition: the local and minio backup repo.

  • local: The default, use the local /pg/backup dir (Softlink point to pg_fs_backup: /data/backups)
  • minio: Use the SNSD 1-node MinIO cluster (Supported by pigsty, but not enabled by default)
pgbackrest_method: local          # choose the backup repo method, `local` or `minio` or any other user defined repo
pgbackrest_repo:                  # pgbackrest repo: https://pgbackrest.org/configuration.html#section-repository
  local:                          # default pgbackrest repo with local posix fs
    path: /pg/backup              # local backup directory, `/pg/backup` by default
    retention_full_type: count    # retention full backups by count
    retention_full: 2             # keep 2, at most 3 full backups when using local fs repo
  minio:                          # optional minio repo for pgbackrest
    type: s3                      # minio is s3-compatible, so s3 is used
    s3_endpoint: sss.pigsty       # minio endpoint domain name, `sss.pigsty` by default
    s3_region: us-east-1          # minio region, us-east-1 by default, useless for minio
    s3_bucket: pgsql              # minio bucket name, `pgsql` by default
    s3_key: pgbackrest            # minio user access key for pgbackrest
    s3_key_secret: S3User.Backup  # minio user secret key for pgbackrest
    s3_uri_style: path            # use path style uri for minio rather than host style
    path: /pgbackrest             # minio backup path, default is `/pgbackrest`
    storage_port: 9000            # minio port, 9000 by default
    storage_ca_file: /etc/pki/ca.crt  # minio ca file path, `/etc/pki/ca.crt` by default
    block: y                      # Enable block incremental backup
    bundle: y                     # bundle small files into a single file
    bundle_limit: 20MiB           # Limit for file bundles, 20MiB for object storage
    bundle_size: 128MiB           # Target size for file bundles, 128MiB for object storage
    cipher_type: aes-256-cbc      # enable AES encryption for remote backup repo
    cipher_pass: pgBackRest       # AES encryption password, default is 'pgBackRest'
    retention_full_type: time     # retention full backup by time on minio repo
    retention_full: 14            # keep full backup for the last 14 days

15.4 - Admin

Manage backup repo and backups

Enable Backup

If you database cluster is created with pgbackrest_enable set to true, the backup will be enabled automatically.

If it created with the false value, you can enable the pgbackrest component with:

./pgsql.yml -t pg_backup    # run the pgbackrest subtask

Remove Backup

Pigsty will remove pgbackrest backup stanza when removing the primary instance (pg_role = primary).

./pgsql-rm.yml
./pgsql-rm.yml -e pg_rm_backup=false   # leave backup intact
./pgsql-rm.yml -t pg_backup            # only remove backup

Use the pg_backup subtask to remove the backup only, and use the pg_rm_backup arg to keep backups.

If your backup repo is locked, (e.g., S3 / MinIO has a lock option), this operation will fail.

Backup Removal

Removing backup may lead to permanent data loss, it’s a dangerous operation, do with extreme caution.


List Backup

This command will list all backups in the pgbackrest repository (shared by all clusters)

pgbackrest info

Manual Backup

Pigsty has a built-in script /pg/bin/pg-backup which wraps the pgbackrest backup command.

pg-backup        # take an incremental backup
pg-backup full   # take an full backup
pg-backup incr   # take an incremental backup
pg-backup diff   # take an differential backup

Base Backup

Pigsty has an alternative backup script /pg/bin/pg-basebackup which does not rely on pgbackrest, and gives you a physical copy of the database cluster. The default backup dir is /pg/backup.

NAME
  pg-basebackup  -- make base backup from PostgreSQL instance

SYNOPSIS
  pg-basebackup -sdfeukr
  pg-basebackup --src postgres:/// --dst . --file backup.tar.lz4

DESCRIPTION
-s, --src, --url     Backup source URL, optional, "postgres:///" by default, if password is required, it should be given in url, ENV or .pgpass
-d, --dst, --dir     Where to put backup files, "/pg/backup" by default
-f, --file           Overwrite default backup filename, "backup_${tag}_${date}.tar.lz4"
-r, --remove         .lz4 Files mtime before n minutes ago will be removed, default is 1200 (20hour)
-t, --tag            Backup file tag, if not set, target cluster_name or local ip address will be used. Also used as part of DEFAULT filename
-k, --key            Encryption key when --encrypt is specified, default key is ${tag}
-u, --upload         Upload backup files to cloud storage, (need your own implementation)
-e, --encryption     Encrypt with RC4 using OpenSSL, if not key is specified, tag is used as key
-h, --help           Print this message
postgres@pg-meta-1:~$ pg-basebackup
[2025-07-13 06:16:05][INFO] ================================================================
[2025-07-13 06:16:05][INFO] [INIT] pg-basebackup begin, checking parameters
[2025-07-13 06:16:05][DEBUG] [INIT] #====== BINARY
[2025-07-13 06:16:05][DEBUG] [INIT] pg_basebackup     :   /usr/pgsql/bin/pg_basebackup
[2025-07-13 06:16:05][DEBUG] [INIT] openssl           :   /usr/bin/openssl
[2025-07-13 06:16:05][DEBUG] [INIT] #====== PARAMETER
[2025-07-13 06:16:05][DEBUG] [INIT] filename  (-f)    :   backup_pg-meta_20250713.tar.lz4
[2025-07-13 06:16:05][DEBUG] [INIT] src       (-s)    :   postgres:///
[2025-07-13 06:16:05][DEBUG] [INIT] dst       (-d)    :   /pg/backup
[2025-07-13 06:16:05][DEBUG] [INIT] tag       (-t)    :   pg-meta
[2025-07-13 06:16:05][DEBUG] [INIT] key       (-k)    :   pg-meta
[2025-07-13 06:16:05][DEBUG] [INIT] encrypt   (-e)    :   false
[2025-07-13 06:16:05][DEBUG] [INIT] upload    (-u)    :   false
[2025-07-13 06:16:05][DEBUG] [INIT] remove    (-r)    :   -mmin +1200
[2025-07-13 06:16:05][INFO] [LOCK] acquire lock @ /tmp/backup.lock
[2025-07-13 06:16:05][INFO] [LOCK] lock acquired success on /tmp/backup.lock, pid=107417
[2025-07-13 06:16:05][INFO] [BKUP] backup begin, from postgres:/// to /pg/backup/backup_pg-meta_20250713.tar.lz4
[2025-07-13 06:16:05][INFO] [BKUP] backup in normal mode
pg_basebackup: initiating base backup, waiting for checkpoint to complete

pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/7000028 on timeline 1
pg_basebackup: write-ahead log end point: 0/7000FD8
pg_basebackup: syncing data to disk ...
pg_basebackup: base backup completed
[2025-07-13 06:16:06][INFO] [BKUP] backup complete!
[2025-07-13 06:16:06][INFO] [RMBK] remove local obsolete backup: 1200
[2025-07-13 06:16:06][INFO] [BKUP] find obsolete backups: find /pg/backup/ -maxdepth 1 -type f -mmin +1200 -name 'backup*.lz4'
[2025-07-13 06:16:06][WARN] [BKUP] remove obsolete backups:
[2025-07-13 06:16:06][INFO] [RMBK] remove old backup complete
[2025-07-13 06:16:06][INFO] [LOCK] release lock @ /tmp/backup.lock
[2025-07-13 06:16:06][INFO] [DONE] backup procedure complete!
[2025-07-13 06:16:06][INFO] ================================================================

Backup are compressed with lz4, You can unzip and extract the tarball with the following command:

mkdir -p /tmp/data   # extract backup to this directory
cat /pg/backup/backup_pg-meta_20250713.tar.lz4 | unlz4 -d -c | tar -xC /tmp/data

Logical Backup

You can also use the pg_dump command to perform a logical backup.

Logical backups cannot be used for PITR (Point In Time Recovery), but they are useful for migrating data between different major versions, or implement flexible data export logic.


Bootstrap from Repo

Now let’s say you have an existing cluster pg-meta, and want to FORK it as pg-meta2:

You’ll need to create the new pg-meta2 cluster fork, then run pitr on it.

15.5 - Restore

Restore PostgreSQL from Backup

You can use the pre-configured pgbackrest to perform Point-in-Time Recovery (PITR) in Pigsty.

  • Manually: PITR with the pg-pitr hint script, do it manually, more flexible with more complexity.
  • Playbook: PITR with the pgsql-pitr.yml playbook, automatic, but less flexible and more error-prone.

If you are very convenient with your configuration, you can use the fully automatic playbook, otherwise, consider do it step by step manually


Quick Start

If you want to roll back the pg-meta cluster to the previous timepoint, adding the pg_pitr:

pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta2
    pg_pitr: { time: '2025-07-13 10:00:00+00' }  # restore from the latest backup

Then run the pgsql-pitr.yml playbook, it will roll back the pg-meta cluster to the specified timepoint.

./pgsql-pitr.yml -l pg-meta

Restore PITR

The archive_mode will be disabled on recovered cluster to prevent unwanted WAL writes. If the recovered database status is ok, you can enable the archive_mode and make a full backup.

postgres @ pg-meta $
psql -c 'ALTER SYSTEM RESET archive_mode; SELECT pg_reload_conf();'
pg-backup full    # take a new full backup

Recovery Target

You can specify different types of recovery targets in pg_pitr, but they are mutually exclusive:

  • time: which time point to restore?
  • name: restore to a named restore point (created by pg_create_restore_point)
  • xid: restore to a specific transaction ID (TXID/XID)
  • lsn: restore to a specific LSN (Log Sequence Number) point

The recovery type will be set accordingly if any of the above parameters is specified, otherwise it will be set to latest (the end of the WAL archive stream). The special immediate type can be used to instruct pgbackrest to minimize the recovery time by stop at the first consistent point.

Target Type

pg_pitr: { }  # restore to the latest status (wal archive stream end)
pg_pitr: { time: "2025-07-13 10:00:00+00" }
pg_pitr: { lsn: "0/4001C80" }
pg_pitr: { xid: "250000" }
pg_pitr: { name: "some_restore_point" }
pg_pitr: { type: "immediate" }

By Time

The most frequently used target is the time point; you can specify the time point to restore to:

restore to a timepoint
./pgsql-pitr.yml -e '{"pg_pitr": { "time": "2025-07-13 10:00:00+00" }}'

Time should be a valid PostgreSQL TIMESTAMP, YYYY-MM-DD HH:MM:SS+TZ is recommended.

By Name

You can create a named restore point with pg_create_restore_point:

SELECT pg_create_restore_point('shit_incoming');

And use that named restore point in PITR:

./pgsql-pitr.yml -e '{"pg_pitr": { "name": "shit_incoming" }}'

By XID

If you have a transaction that accidentally deleted some data, the best way to recover is to restore the database to the state before that transaction.

restore right before a transaction
./pgsql-pitr.yml -e '{"pg_pitr": { "xid": "250000", exclusive: true }}'

You can find the exact transaction id from monitoring dashboard, or find it from TXID from the CSVLOG.

Inclusive vs Exclusive

The target parameter is “inclusive” by default, which means the recovery will include the target point. The exclusive flag will exclude that exact target, like the xid 24999 will be the last transaction being replayed

This only applies to time, xid, lsn recovery targets, check recovery_target_inclusive for details.

By LSN

PostgreSQL uses the LSN (Log Sequence Number) to identify the position of a WAL record. You can find it everywhere, like the PG LSN panel from Pigsty dashboards.

restore to a LSN
./pgsql-pitr.yml -e '{"pg_pitr": { "lsn": "0/4001C80", timeline: "1" }}'

To restore to an exact point in the WAL stream, you may also specify the timeline parameter (default to latest)


Recovery Source

  • cluster: which cluster to restore? the current pg_cluster will be used by default, you can use any other cluster in the same pgbackrest repo
  • repo: overwrite the backup repo, use the same format in pgbackrest_repo
  • set: the latest backup set is used by default, but you can specify a specific pgbackrest backup by label

Pigsty will recover from the pgbackrest backup repository, if you are using a centralized backup repo (like MinIO/S3), you can specify another “stanza” (another cluster’s backup directory) to restore from.

pg-meta2:
  hosts: { 10.10.10.11: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta2
    pg_pitr: { cluster: pg-meta }  # restore from the pg-meta cluster backup

The above configuration will mark the PITR procedure to use the pg-meta stanza. You can also pass the pg_pitr parameter via CLI args:

pitr pg-meta2 with pg-meta backup
./pgsql-pitr.yml -l pg-meta2 -e '{"pg_pitr": { "cluster": "pg-meta" }}'

You can also use these targets when pitr from another cluster:

./pgsql-pitr.yml -l pg-meta2 -e '{"pg_pitr": { "cluster": "pg-meta", "time": "2025-07-14 08:00:00+00" }}'

Break Down

This approach is semi-automatic, you will participate in the PITR process to make key decisions.

For example, this configuration will restore the pg-meta cluster itself to the specified timepoint

pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta2
    pg_pitr: { time: '2025-07-13 10:00:00+00' }  # restore from the latest backup

Let’s do this one step by step:

./pgsql-pitr.yml -l pg-meta -t down     # pause patroni HA
./pgsql-pitr.yml -l pg-meta -t pitr     # run the pitr procedure
./pgsql-pitr.yml -l pg-meta -t up       # generate pgbackrest config and restore script
# down                 : # stop ha and shutdown patroni and postgres
#   - pause            : # pause patroni auto failover
#   - stop             : # stop patroni and postgres service
#     - stop_patroni   : # stop patroni service
#     - stop_postgres  : # stop postgres service
# pitr                 : # perform the PITR procedure
#   - config           : # generate pgbackrest config and restore script
#   - restore          : # run the pgbackrest restore command
#   - recovery         : # start postgres and complete recovery
#   - verify           : # verify the recovered cluster control data
# up:                  : # start postgres / patroni and resume ha
#   - etcd             : # clean up etcd metadata before launching
#   - start            : # start patroni and postgres service
#     - start_postgres : # start postgres service
#     - start_patroni  : # start patroni service
#   - resume           : # resume patroni auto failover

PITR Definition

There are more options available in the pg_pitr parameter:

pg_pitr:                        # define a PITR task
    cluster: "some_pg_cls_name"   # Source cluster name
    type: latest                  # Recovery target type: time, xid, name, lsn, immediate, latest
    time: "2025-01-01 10:00:00+00" # Recovery target: time, exclusive with xid, name, lsn
    name: "some_restore_point"    # Recovery target: named restore point, exclusive with time, xid, lsn
    xid:  "100000"                # Recovery target: transaction ID, exclusive with time, name, lsn
    lsn:  "0/3000000"             # Recovery target: log sequence number, exclusive with time, name, xid
    timeline: latest              # Target timeline, can be an integer, latest by default,
    exclusive: false              # Exclude the target point, default false?
    action: pause                 # Post-recovery action: pause, promote, shutdown
    archive: false                # Preserve archive settings? false by default
    db_exclude: [ template0, template1 ]
    db_include: []
    link_map:
      pg_wal: '/data/wal'
      pg_xact: '/data/pg_xact'
    process: 4                    # Parallel restore processes
    repo: {}                      # Repository to restore from
    data: /pg/data                # where to restore the data
    port: 5432                    # listen port of the recovered instance

15.6 - Example

Perform PITR manually in sandbox according to hint script

You can do PITR with the pgsql-pitr playbook, while in some case, you may want to perform PITR manually. We’ll illustrate the procedure with the 4-node sandbox cluster with minio backup repo.


Init Sandbox

Prepare the 4-node sandbox environment with vagrant or terraform, then:

curl https://repo.pigsty.io/get | bash -s v3.7.0; cd ~/pigsty/
./configure -c full
./install

Now operate as the admin user (or dbsu) on the admin node to proceed.

pigsty-sandbox.jpg

Check Backup

To check the backup status, you’ll need to switch to the postgres user and use the pb command:

sudo su - postgres    # switch to the dbsu: postgres user
pb info               # print pgbackrest backup info

The pb is the alias for pgbackrest, with auto scraped stanza name from pgbackrest config.

/etc/profile.d/pg-alias.sh
function pb() {
    local stanza=$(grep -o '\[[^][]*]' /etc/pgbackrest/pgbackrest.conf | head -n1 | sed 's/.*\[\([^]]*\)].*/\1/')
    pgbackrest --stanza=$stanza $@
}

You can see the initial backup info, which is a full backup created at

root@pg-meta-1:~# pb info
stanza: pg-meta
    status: ok
    cipher: aes-256-cbc

    db (current)
        wal archive min/max (17): 000000010000000000000001/000000010000000000000007

        full backup: 20250713-022731F
            timestamp start/stop: 2025-07-13 02:27:31+00 / 2025-07-13 02:27:33+00
            wal start/stop: 000000010000000000000004 / 000000010000000000000004
            database size: 44MB, database backup size: 44MB
            repo1: backup size: 8.4MB

The backup finish at 2025-07-13 02:27:33+00, this is the earliest time you can restore to. Since wal archive is active, you can restore to any point in time after the backup, until the WAL end (now).


Generate Heartbeat

You can generate some heartbeat to simulate the workload. the /pg-bin/pg-heartbeat is for this purpose, It will write a heartbeat timestamp to the monitor.heartbeat table every second.

make rh     # run heartbeat: ssh 10.10.10.10 'sudo -iu postgres /pg/bin/pg-heartbeat'
ssh 10.10.10.10 'sudo -iu postgres /pg/bin/pg-heartbeat'
   cls   |              ts               |    lsn     |  lsn_int  | txid | status  |       now       |  elapse
---------+-------------------------------+------------+-----------+------+---------+-----------------+----------
 pg-meta | 2025-07-13 03:01:20.318234+00 | 0/115BF5C0 | 291239360 | 4812 | leading | 03:01:20.318234 | 00:00:00

You can even add more workload to the cluster, let’s use pgbench to generate some random writes:

make ri     # init pgbench
make rw     # run pgbench rw workload
pgbench -is10 postgres://dbuser_meta:[email protected]:5433/meta
while true; do pgbench -nv -P1 -c4 --rate=64 -T10 postgres://dbuser_meta:[email protected]:5433/meta; done
while true; do pgbench -nv -P1 -c4 --rate=64 -T10 postgres://dbuser_meta:[email protected]:5433/meta; done
pgbench (17.5 (Homebrew), server 17.4 (Ubuntu 17.4-1.pgdg24.04+2))
progress: 1.0 s, 60.9 tps, lat 7.295 ms stddev 4.219, 0 failed, lag 1.818 ms
progress: 2.0 s, 69.1 tps, lat 6.296 ms stddev 1.983, 0 failed, lag 1.397 ms
...

PITR Manual

Now let’s choose a time point to recovery, let’s say 2025-07-13 03:03:03+00, which is a timepoint after the initial backup (and heartbeat). To perform the manual PITR, use the pg-pitr util:

$ pg-pitr -t "2025-07-13 03:03:00+00"

It will generate the instructions for you to perform the recovery, it usually takes four steps:

Perform time PITR on pg-meta
[1. Stop PostgreSQL] ===========================================
   1.1 Pause Patroni (if there are any replicas)
       $ pg pause <cls>  # pause patroni auto failover
   1.2 Shutdown Patroni
       $ pt-stop         # sudo systemctl stop patroni
   1.3 Shutdown Postgres
       $ pg-stop         # pg_ctl -D /pg/data stop -m fast

[2. Perform PITR] ===========================================
   2.1 Restore Backup
       $ pgbackrest --stanza=pg-meta --type=time --target='2025-07-13 03:03:00+00' restore
   2.2 Start PG to Replay WAL
       $ pg-start        # pg_ctl -D /pg/data start
   2.3 Validate and Promote
     - If database content is ok, promote it to finish recovery, otherwise goto 2.1
       $ pg-promote      # pg_ctl -D /pg/data promote

[3. Restore Primary] ===========================================
   3.1 Enable Archive Mode (Restart Required)
       $ psql -c 'ALTER SYSTEM SET archive_mode = on;'
   3.1 Restart Postgres to Apply Changes
       $ pg-restart      # pg_ctl -D /pg/data restart
   3.3 Restart Patroni
       $ pt-restart      # sudo systemctl restart patroni

[4. Restore Cluster] ===========================================
   4.1 Re-Init All [**REPLICAS**] (if any)
       - 4.1.1 option 1: restore replicas with same pgbackrest cmd (require central backup repo)
           $ pgbackrest --stanza=pg-meta --type=time --target='2025-07-13 03:03:00+00' restore
       - 4.1.2 option 2: nuke the replica data dir and restart patroni (may take long time to restore)
           $ rm -rf /pg/data/*; pt-restart
       - 4.1.3 option 3: reinit with patroni, which may fail if primary lsn < replica lsn
           $ pg reinit pg-meta
   4.2 Resume Patroni
       $ pg resume pg-meta
   4.3 Full Backup (optional)
       $ pg-backup full      # IT's recommend to make a new full backup after PITR

Single-Node Example

Let’s start with the simple 1-node pg-meta cluster as an example, which is simpler.

Shutdown Database

pt-stop         # sudo systemctl stop patroni, shutdown patroni (and postgres)
$ pg_stop        # pg_ctl -D /pg/data stop -m fast, shutdown postgres

pg_ctl: PID file "/pg/data/postmaster.pid" does not exist
Is server running?

$ pg-ps           # print postgres related processes

UID         PID   PPID  C STIME TTY      STAT   TIME CMD
postgres  31048      1  0 02:27 ?        Ssl    0:19 /usr/sbin/pgbouncer /etc/pgbouncer/pgbouncer.ini
postgres  32026      1  0 02:28 ?        Ssl    0:03 /usr/bin/pg_exporter --web.listen-address=:9630 --log.level=info
postgres  32252      1  0 02:28 ?        Ssl    0:00 /usr/bin/pg_exporter --web.listen-address=:9631 --log.level=info
postgres  32460      1  0 02:28 ?        Ssl    0:00 /usr/bin/pgbackrest_exporter --log.level=info
postgres  35480  35479  0 03:00 pts/2    S      0:00 -bash
postgres  35510  35480  0 03:01 pts/2    S+     0:00 /bin/bash /pg/bin/pg-heartbeat
postgres  37183  37182  0 03:07 pts/4    S      0:00 -bash
postgres  38627  35510  0 03:14 pts/2    S+     0:00 sleep 1

Make sure the local postgres is not running, then perform the recovery command given in the manual:

Restore Backup

pgbackrest --stanza=pg-meta --type=time --target='2025-07-13 03:03:00+00' restore
postgres@pg-meta-1:~$ pgbackrest --stanza=pg-meta --type=time --target='2025-07-13 03:03:00+00' restore
2025-07-13 03:17:07.443 P00   INFO: restore command begin 2.54.2: --archive-mode=off --delta --exec-id=38997-5c07abb3 --log-level-console=info --log-level-file=info --log-path=/pg/log/pgbackrest --pg1-path=/pg/data --process-max=2 --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/pgbackrest --repo1-s3-bucket=pgsql --repo1-s3-endpoint=sss.pigsty --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-s3-uri-style=path --repo1-storage-ca-file=/etc/pki/ca.crt --repo1-storage-port=9000 --repo1-type=s3 --spool-path=/pg/spool --stanza=pg-meta --target="2025-07-13 03:03:00+00" --type=time
2025-07-13 03:17:07.470 P00   INFO: repo1: restore backup set 20250713-022731F, recovery will start at 2025-07-13 02:27:31
2025-07-13 03:17:07.471 P00   INFO: remove invalid files/links/paths from '/pg/data'
2025-07-13 03:17:08.523 P00   INFO: write updated /pg/data/postgresql.auto.conf
2025-07-13 03:17:08.526 P00   INFO: restore global/pg_control (performed last to ensure aborted restores cannot be started)
2025-07-13 03:17:08.527 P00   INFO: restore size = 44MB, file total = 1436
2025-07-13 03:17:08.527 P00   INFO: restore command end: completed successfully (1087ms)

Validate Data

We don’t want patroni HA to take over until we are sure the data is correct, so we start postgres manually:

pg-start
waiting for server to start....2025-07-13 03:19:33.133 UTC [39294] LOG:  redirecting log output to logging collector process
2025-07-13 03:19:33.133 UTC [39294] HINT:  Future log output will appear in directory "/pg/log/postgres".
 done
server started

Now you can check the data to see if the it is at the timepoint you want. You can validate it by checking some latest timestamp from business tables, or in this case, check via the heartbeat table.

postgres@pg-meta-1:~$ psql -c 'table monitor.heartbeat'
   id    |              ts               |    lsn    | txid
---------+-------------------------------+-----------+------
 pg-meta | 2025-07-13 03:02:59.214104+00 | 302005504 | 4912

The timestamp is right before the timepoint we specified! (2025-07-13 03:03:00+00). If this is not the timepoint you want, you can repeat the restore with a different timepoint. It’s rapid since recovery is performed in an incremental and parallel way. It’s ok to retry until you get the right point.

Promote Leader

The recovered postgres cluster is in recovery mode, so it will reject any write operations until you promote it to primary. These recovery params are generated by pgBackRest in the config file.

/pg/data/postgresql.auto.conf
postgres@pg-meta-1:~$ cat /pg/data/postgresql.auto.conf
# Do not edit this file or use ALTER SYSTEM manually!
# It is managed by Pigsty & Ansible automatically!

# Recovery settings generated by pgBackRest restore on 2025-07-13 03:17:08
archive_mode = 'off'
restore_command = 'pgbackrest --stanza=pg-meta archive-get %f "%p"'
recovery_target_time = '2025-07-13 03:03:00+00'

If data is correct, you can promote it to primary, mark it as the new leader and ready to accept writes.

pg-promote
waiting for server to promote.... done
server promoted
psql -c 'SELECT pg_is_in_recovery()'   # the 'f' means it is promoted to primary
 pg_is_in_recovery
-------------------
 f
(1 row)
New Timeline and Split Brain

Once promoted, the database cluster will enter a new timeline (the leader epoch). If there’s any write traffic, it will be written to the new timeline.

Restore Cluster

Finally, it’s not only the data that need recovery, but also the cluster state, such as:

  • patroni takeover
  • archive mode
  • backup set
  • replicas

Patroni Takeover

You postgres is start directly, to restore HA takeover; you’ll have to start the patroni service:

pt-start   # sudo systemctl start patroni
pg resume pg-meta      # resume patroni auto failover (if you have paused it before)

Archive Mode

The archive_mode is disabled by pgbackrest during recovery。 If you want the new leader’s writes to be archived in the backup repo, you also need to enable the archive_mode config.

psql -c 'show archive_mode'

 archive_mode
--------------
 off
psql -c 'ALTER SYSTEM RESET archive_mode;'
psql -c 'SELECT pg_reload_conf();'
psql -c 'show archive_mode'
# you can also edit the postgresql.auto.conf directly and reload with pg_ctl
sed -i '/archive_mode/d' /pg/data/postgresql.auto.conf
pg_ctl -D /pg/data reload

Backup Set

It’s usually a good idea to take a new full backup after PITR, but it’s optional.

Replicas

If your postgres cluster has replicas, you’ll need to perform the PITR on each replica as well. Or, the simple way is to nuke the replica data directory and restart patroni, which will re-initialize the replica from the primary. We will cover this case in the next multi-node cluster example.


Multi-Node Example

Now let’s play with the 3-node pg-test cluster as an PITR example.

16 - Kernel

Replace vanilla PostgreSQL with exotic kernel forks

Pigsty supports various PostgreSQL kernels and compatible forks, enabling you to simulate different database systems while leveraging PostgreSQL’s ecosystem. Each kernel provides unique capabilities and compatibility layers.

Database Kernels

PostgreSQL

Vanilla Postgres with 437 Extensions

Citus

Native Distributive Extension

Babelfish

SQL Server wire-compatible

IvorySQL

Oracle grammar & PL/SQL compatible

OpenHalo

MySQL wire-compatibility

Percona

Transparent Data Encryption

OrioleDB

OLTP-optimized cloud-native storage engine

PolarDB PG

Aurora-like RAC with china domestic compliance

Supabase

Backend as a Service, self-hosting Firebase

FerretDB

Mongo Wire-Compatibility over PostgreSQL


Choose the Right Kernel

Note

Flexible Kernel: Choose the right kernel for your specific use case - whether you need MSSQL compatibility, Oracle features, or horizontal scaling capabilities.

Kernel Key Feature Description
PostgreSQL Original Flavor Vanilla PostgreSQL with 437 extensions
Citus Horizontal Scaling Distributive PostgreSQL via native extension
WiltonDB SQL Server Migration SQL Server wire-compatibility
IvorySQL Oracle Migration Oracle Grammar and PL/SQL compatible
OpenHalo MySQL Migration MySQL wire-protocol compatibility
Percona Transparent Data Encryption Percona Distribution with pg_tde
FerretDB MongoDB Migration MongoDB wire-protocol compatibility
OrioleDB OLTP Optimization Zheap, No bloat, S3 Storage
PolarDB Aurora flavor RAC RAC, China domestic compliance
Supabase Backend as Service BaaS based on PostgreSQL, Firebase alternative
Cloudberry (WIP) MPP DW & Analytics Massively parallel processing database warehouse

Citus (Distributive)

Citus Native Distributive

Citus transforms PostgreSQL into a distributed database system, enabling horizontal scaling across multiple nodes. Deploy native HA Citus clusters with Pigsty for better throughput and performance.

Key Features

  • Distributed Tables: Automatically shard tables across worker nodes
  • Distributed Queries: Execute queries across the entire cluster
  • High Availability: Built-in replication and failover capabilities
  • Real-time Analytics: Handle both transactional and analytical workloads
  • Postgres Compatibility: Maintain full PostgreSQL feature compatibility

Use Cases

  • Multi-tenant SaaS applications requiring horizontal scaling
  • Real-time analytics on large datasets
  • High-throughput OLTP workloads
  • Applications need to scale beyond single-node limitations
Note

Planning Required: Proper shard key selection is crucial for optimal performance and avoiding cross-shard queries.


Babelfish (MSSQL)

Babelfish SQL Server Wire Compatible

Note

SQL Server Compatible

Note

Beta

Create SQL Server-compatible PostgreSQL clusters with WiltonDB and Babelfish, providing wire protocol-level compatibility with Microsoft SQL Server.

Key Features

  • T-SQL Support: Execute T-SQL queries natively
  • Wire Protocol Compatibility: Connect using SQL Server drivers and tools
  • Stored Procedures: Support for T-SQL stored procedures and functions
  • Data Types: Compatible with SQL Server data types and behaviors
  • Migration Tools: Simplified migration from SQL Server environments

Use Cases

  • Migrating legacy SQL Server applications to PostgreSQL
  • Multi-database environments requiring SQL Server compatibility
  • Cost reduction while maintaining application compatibility
  • Cloud migration from SQL Server to open-source alternatives
Note

Migration Path: Ideal for organizations looking to reduce licensing costs while maintaining existing SQL Server application compatibility.


IvorySQL (Oracle)

Babelfish Oracle Grammar Compatible

Run Oracle-compatible PostgreSQL clusters with the IvorySQL kernel, open-sourced by HighGo, providing Oracle syntax and feature compatibility.

Key Features

  • PL/SQL Support: Execute PL/SQL code with minimal modifications
  • Oracle Syntax: Support for Oracle-specific SQL syntax and functions
  • Package Support: Oracle-style package and procedure definitions
  • Data Types: Oracle-compatible data types and behaviors
  • Migration Tools: Utilities for Oracle to PostgreSQL migration

Use Cases

  • Oracle database migration projects
  • Organizations seeking Oracle feature compatibility
  • Cost optimization while preserving Oracle functionality
  • Development environments requiring Oracle compatibility
Note

Enterprise Focus: Particularly valuable for enterprises with significant Oracle investments looking for migration paths.


OpenHalo (MySQL)

OpenHalo MySQL Wire-Compatible

The OpenHalo kernel provides MySQL-compatible PostgreSQL functionality, accessible using standard MySQL clients and protocols.

Key Features

  • MySQL Protocol: Wire-level compatibility with MySQL protocol
  • Client Compatibility: Use existing MySQL drivers and tools
  • SQL Dialect: Support for MySQL-specific SQL syntax
  • Migration Support: Simplified migration from MySQL environments
  • Ecosystem Integration: Leverage PostgreSQL’s advanced features with MySQL compatibility

Use Cases

  • MySQL application migration to PostgreSQL
  • Multi-database environments requiring MySQL compatibility
  • Leveraging PostgreSQL features while maintaining MySQL interface
  • Gradual migration strategies from MySQL to PostgreSQL
Note

Early Stage: Currently experimental - evaluate thoroughly before production use.


OrioleDB (OLTP)

OrioleDB OLTP Optimized Cloud Native

A PostgreSQL storage engine optimized for OLTP workloads, eliminating transaction ID wraparound issues and table bloat while supporting cloud storage.

Compatible with PostgreSQL 17, Available on all support platforms.

Key Features

  • No XID Wraparound: Eliminates transaction ID wraparound maintenance
  • No Table Bloat: Advanced storage management prevents table bloat
  • Cloud Storage: Native support for S3-compatible object storage
  • OLTP Optimization: Specifically designed for transactional workloads
  • Improved Performance: Better space utilization and query performance

Use Cases

  • High-frequency transactional applications
  • Cloud-native deployments requiring object storage
  • Applications suffering from PostgreSQL maintenance overhead
  • Systems requiring consistent performance without vacuum cycles
Note

Early Stage: Currently in Beta - evaluate thoroughly before production use.


PolarDB PG (RAC)

PolarDB Aurora Flavor RAC

Replace vanilla PostgreSQL with PolarDB PG, an open-source Aurora-like solution similar to Oracle RAC with shared storage architecture.

Key Features

  • Shared Storage: Multiple compute nodes sharing the same storage layer
  • Read Scaling: Add read replicas without storage duplication
  • Fast Recovery: Rapid recovery through shared storage architecture
  • Cost Efficiency: Reduced storage costs through sharing
  • High Availability: Built-in failover and disaster recovery

Use Cases

  • Applications requiring extreme read scalability
  • Cost-sensitive deployments needing high availability
  • Cloud environments with shared storage infrastructure
  • Workloads with variable read/write patterns
Note

Cloud Architecture: Designed for cloud environments with disaggregated compute and storage.


Supabase (Firebase)

Supabase Backend as a Service

Self-host Supabase with existing managed HA PostgreSQL clusters, launching the stateless components with docker-compose for a complete Firebase alternative.

Key Features

  • Real-time APIs: Auto-generated REST and GraphQL APIs
  • Real-time Subscriptions: WebSocket-based real-time data sync
  • Authentication: Built-in user authentication and authorization
  • Storage: File storage with CDN capabilities
  • Edge Functions: Serverless functions for custom logic

Use Cases

  • Rapid application development with backend-as-a-service
  • Real-time applications requiring instant data sync
  • JAMstack applications needing serverless backend
  • Mobile and web apps require authentication and storage
Note

Full Stack: Provides a complete backend solution with PostgreSQL as the foundation.


Cloudberry (MPP)

Cloudberry MPP Data Warehouse

Install and monitor Greenplum / Cloudberry / YMatrix MPP clusters with Pigsty for large-scale analytical processing and data warehousing.

Key Features

  • Massively Parallel Processing: Distribute queries across multiple nodes
  • Columnar Storage: Optimized storage for analytical workloads
  • Advanced Analytics: Built-in machine learning and statistical functions
  • Petabyte Scale: Handle massive datasets with linear scalability
  • Standard SQL: Full SQL compliance with PostgreSQL compatibility

Use Cases

  • Data warehousing and business intelligence
  • Large-scale analytics and reporting
  • Machine learning on big datasets
  • ETL processing for enterprise data platforms
Note

Enterprise Analytics: Designed for enterprise-scale analytical workloads requiring massive parallel processing capabilities.

16.1 - PostgreSQL

The vanilla PostgreSQL kernel with 437 extensions

PostgreSQL is the most advanced & popular open source database in the world.

Pigsty supports PostgreSQL 13 ~ 18, and provides 437 extensions alone with it.


Get Started

install Pigsty’s with the pgsql config template.

curl -fsSL https://repo.pigsty.io/get | bash -s v3.7.0; cd ~/pigsty;
./configure -c pgsql     # use vanilla postgres kernel
./install.yml            # setup everything with pigsty

Most of the config template is use the PostgreSQL kernel by default, such as:

  • meta : DEFAULT, postgres with core extensions (vector, postgis, timescale)
  • rich : postgres with all extensions installed
  • slim : postgres only without monitor infra
  • full : the 4-node sandbox for HA demonstration
  • pgsql : the minimal postgres kernel config example (THIS CONFIG)

Configure

Nothing special needs to be tuned for vanilla PostgreSQL kernel:

pg-meta:
  hosts:
    10.10.10.10: { pg_seq: 1, pg_role: primary }
  vars:
    pg_cluster: pg-meta
    pg_users:
      - { name: dbuser_meta ,password: DBUser.Meta   ,pgbouncer: true ,roles: [dbrole_admin   ] ,comment: pigsty admin user }
      - { name: dbuser_view ,password: DBUser.Viewer ,pgbouncer: true ,roles: [dbrole_readonly] ,comment: read-only viewer  }
    pg_databases:
      - { name: meta, baseline: cmdb.sql ,comment: pigsty meta database ,schemas: [pigsty] ,extensions: [ vector ]}
    pg_hba_rules:
      - { user: dbuser_view , db: all ,addr: infra ,auth: pwd ,title: 'allow grafana dashboard access cmdb from infra nodes' }
    node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ] # make a full backup every 1am
    pg_packages: [ pgsql-main, pgsql-common ]   # pg kernel and common utils
    #pg_extensions: [ pg18-time ,pg18-gis ,pg18-rag ,pg18-fts ,pg18-olap ,pg18-feat ,pg18-lang ,pg18-type ,pg18-util ,pg18-func ,pg18-admin ,pg18-stat ,pg18-sec ,pg18-fdw ,pg18-sim ,pg18-etl]

To use a different PostgreSQL major version, you can configure with -v parameter:

./configure -c pgsql            # the default is postgresql 18
./configure -c pgsql -v 17      # use postgresql 17
./configure -c pgsql -v 16      # use postgresql 16
./configure -c pgsql -v 15      # use postgresql 15
./configure -c pgsql -v 14      # use postgresql 14
./configure -c pgsql -v 13      # use postgresql 13

If PostgreSQL cluster is already installed, you’ll need to uninstall it before installing the new version

./pgsql-rm.yml # -l pg-meta

PostgreSQL beta

To use PostgreSQL beta version (19 not released), you’ll also need to add the beta repo to your node_repo_modules (or repo_modules if you are building a local repo)

The configure will do that for you if you are use the -v 19 argument:

./configure -c pgsql -v 19     # use the postgresql 19 kernel (beta not released yet)
./install.yml                  # setup everything with pigsty

Beware Pigsty is not build extensions for PostgreSQL 19 yet, so only those existing extensions in the PGDG repo are currently available. We will start building them after the PostgreSQL 19 is released.


Multi-Node

To setup a multi-node PostgreSQL cluster, you can check the PGSQL: Configure for details:

This is the example 3-node pg-test cluster in the full config template:

16.2 - Citus

Native Distributive Extension for PostgreSQL Sharding

Citus is a PostgreSQL extension that transforms PostgreSQL into a distributed database, enabling horizontal scaling across multiple nodes to handle large amounts of data and queries.

Since Patroni v3.0, native support for Citus high availability has been provided, simplifying the setup of Citus clusters. Pigsty also offers native support for this.

Pigsty v3.7.0 pins the Citus template to PostgreSQL 17; Citus packages are not available for PostgreSQL 18 in this release.


Citus Cluster

Pigsty natively supports Citus. Refer to conf/citus.yml.

This example uses a four-node sandbox with a Citus cluster named pg-citus, consisting of a two-node coordinator cluster pg-citus0 and two worker clusters pg-citus1 and pg-citus2.

pg-citus:
  hosts:
    10.10.10.10: { pg_group: 0, pg_cluster: pg-citus0 ,pg_vip_address: 10.10.10.2/24 ,pg_seq: 1, pg_role: primary }
    10.10.10.11: { pg_group: 0, pg_cluster: pg-citus0 ,pg_vip_address: 10.10.10.2/24 ,pg_seq: 2, pg_role: replica }
    10.10.10.12: { pg_group: 1, pg_cluster: pg-citus1 ,pg_vip_address: 10.10.10.3/24 ,pg_seq: 1, pg_role: primary }
    10.10.10.13: { pg_group: 2, pg_cluster: pg-citus2 ,pg_vip_address: 10.10.10.4/24 ,pg_seq: 1, pg_role: primary }
  vars:
    pg_mode: citus                            # pgsql cluster mode: citus
    pg_version: 17                            # Citus is not available for PG18 in v3.7.0
    pg_shard: pg-citus                        # Citus shard name: pg-citus
    pg_primary_db: citus                      # primary database used by Citus
    pg_vip_enabled: true                      # enable VIP for Citus cluster
    pg_vip_interface: eth1                    # VIP interface for all members
    pg_dbsu_password: DBUser.Postgres         # all DBSU passwords for Citus cluster
    pg_extensions: [ citus, postgis, pgvector, topn, pg_cron, hll ]  # install these extensions
    pg_libs: 'citus, pg_cron, pg_stat_statements' # Citus will be added automatically by Patroni
    pg_users: [{ name: dbuser_citus ,password: DBUser.Citus ,pgbouncer: true ,roles: [ dbrole_admin ]    }]
    pg_databases: [{ name: citus ,owner: dbuser_citus ,extensions: [ citus, vector, topn, pg_cron, hll ] }]
    pg_parameters:
      cron.database_name: citus
      citus.node_conninfo: 'sslmode=require sslrootcert=/pg/cert/ca.crt sslmode=verify-full'
    pg_hba_rules:
      - { user: 'all' ,db: all  ,addr: 127.0.0.1/32  ,auth: ssl   ,title: 'all user ssl access from localhost' }
      - { user: 'all' ,db: all  ,addr: intra         ,auth: ssl   ,title: 'all user ssl access from intranet'  }

Compared to a standard PostgreSQL cluster, Citus cluster configuration has some specific requirements. First, ensure that the Citus extension is downloaded, installed, loaded, and enabled. This involves the following four parameters:

  • repo_packages: Must include the citus extension, or you need to use a PostgreSQL offline package with the Citus extension.
  • pg_extensions: Must include the citus extension, meaning you need to install the citus extension on each node.
  • pg_libs: Must include the citus extension, and it must be first in the list, but now Patroni will automatically handle this.
  • pg_databases: Define a primary database with the citus extension installed.

Additionally, ensure the configuration for the Citus cluster is correct:

  • pg_mode: Must be set to citus to inform Patroni to use the Citus mode.
  • pg_primary_db: Specify the primary database name, which must have the citus extension (named citus here).
  • pg_shard: Specify a unified name as a prefix for all horizontal shard PG clusters (e.g., pg-citus).
  • pg_group: Specify a shard number, starting from zero for the coordinator cluster and incrementing for worker clusters.
  • pg_cluster: Must match the combination of [pg_shard] and [pg_group].
  • pg_dbsu_password: Set a non-empty plain-text password for proper Citus functionality.
  • pg_parameters: It is recommended to set the citus.node_conninfo parameter, which enforces SSL access and requires node-to-node client certificate verification.

Once configured, deploy the Citus cluster just like a regular PostgreSQL cluster using pgsql.yml.


Managing Citus Clusters

After defining the Citus cluster, use the same playbook pgsql.yml to deploy the Citus cluster:

./pgsql.yml -l pg-citus    # Deploy Citus cluster pg-citus

Any DBSU user (postgres) can use patronictl (alias: pg) to list the status of the Citus cluster:

$ pg list
+ Citus cluster: pg-citus ----------+---------+-----------+----+-----------+--------------------+
| Group | Member      | Host        | Role    | State     | TL | Lag in MB | Tags               |
+-------+-------------+-------------+---------+-----------+----+-----------+--------------------+
|     0 | pg-citus0-1 | 10.10.10.10 | Leader  | running   |  1 |           | clonefrom: true    |
|       |             |             |         |           |    |           | conf: tiny.yml     |
|       |             |             |         |           |    |           | spec: 20C.40G.125G |
|       |             |             |         |           |    |           | version: '17'      |
+-------+-------------+-------------+---------+-----------+----+-----------+--------------------+
|     1 | pg-citus1-1 | 10.10.10.11 | Leader  | running   |  1 |           | clonefrom: true    |
|       |             |             |         |           |    |           | conf: tiny.yml     |
|       |             |             |         |           |    |           | spec: 10C.20G.125G |
|       |             |             |         |           |    |           | version: '17'      |
+-------+-------------+-------------+---------+-----------+----+-----------+--------------------+
|     2 | pg-citus2-1 | 10.10.10.12 | Leader  | running   |  1 |           | clonefrom: true    |
|       |             |             |         |           |    |           | conf: tiny.yml     |
|       |             |             |         |           |    |           | spec: 10C.20G.125G |
|       |             |             |         |           |    |           | version: '17'      |
+-------+-------------+-------------+---------+-----------+----+-----------+--------------------+
|     2 | pg-citus2-2 | 10.10.10.13 | Replica | streaming |  1 |         0 | clonefrom: true    |
|       |             |             |         |           |    |           | conf: tiny.yml     |
|       |             |             |         |           |    |           | spec: 10C.20G.125G |
|       |             |             |         |           |    |           | version: '17'      |
+-------+-------------+-------------+---------+-----------+----+-----------+--------------------+

Each horizontal shard cluster can be treated as a separate PGSQL cluster, managed with the pg (patronictl) command. Note that when using pg to manage the Citus cluster, the --group parameter must be used to specify the cluster shard number:

pg list pg-citus --group 0   # Use --group 0 to specify the shard number

Citus has a system table called pg_dist_node to record node information, which Patroni automatically maintains.

PGURL=postgres://postgres:[email protected]/citus

psql $PGURL -c 'SELECT * FROM pg_dist_node;'       # View node information

Additionally, you can view user authentication information (restricted to superusers):

$ psql $PGURL -c 'SELECT * FROM pg_dist_authinfo;'   # View node authentication info (superuser only)

You can then access the Citus cluster with regular business users (e.g., dbuser_citus with DDL permissions):

psql postgres://dbuser_citus:[email protected]/citus -c 'SELECT * FROM pg_dist_node;'

Using the Citus Cluster

When using a Citus cluster, we highly recommend reading the Citus Official Documentation to understand its architecture and core concepts.

Key to this is understanding the five types of tables in Citus, their characteristics, and use cases:

  • Distributed Table
  • Reference Table
  • Local Table
  • Local Management Table
  • Schema Table

On the coordinator node, you can create distributed and reference tables and query them from any data node. Since version 11.2, any Citus database node can act as a coordinator.

We can use pgbench to create some tables, distributing the main table (pgbench_accounts) across the nodes, and using other smaller tables as reference tables:

PGURL=postgres://dbuser_citus:[email protected]/citus
pgbench -i $PGURL

psql $PGURL <<-EOF
SELECT create_distributed_table('pgbench_accounts', 'aid'); SELECT truncate_local_data_after_distributing_table('public.pgbench_accounts');
SELECT create_reference_table('pgbench_branches')         ; SELECT truncate_local_data_after_distributing_table('public.pgbench_branches');
SELECT create_reference_table('pgbench_history')          ; SELECT truncate_local_data_after_distributing_table('public.pgbench_history');
SELECT create_reference_table('pgbench_tellers')          ; SELECT truncate_local_data_after_distributing_table('public.pgbench_tellers');
EOF

Run read-write bench:

pgbench -nv -P1 -c10 -T500 postgres://dbuser_citus:[email protected]/citus      # 直连协调者 5432 端口
pgbench -nv -P1 -c10 -T500 postgres://dbuser_citus:[email protected]:6432/citus # 通过连接池,减少客户端连接数压力,可以有效提高整体吞吐。
pgbench -nv -P1 -c10 -T500 postgres://dbuser_citus:[email protected]/citus      # 任意 primary 节点都可以作为 coordinator
pgbench --select-only -nv -P1 -c10 -T500 postgres://dbuser_citus:[email protected]/citus # 可以发起只读查询

Production Deployment

Production citus deployment usually requires physical replication for both coordinator and each worker cluster.

For example, in simu.yml there’s a 10-node cluster cluster:

pg-citus: # citus group
  hosts:
    10.10.10.50: { pg_group: 0, pg_cluster: pg-citus0 ,pg_vip_address: 10.10.10.60/24 ,pg_seq: 0, pg_role: primary }
    10.10.10.51: { pg_group: 0, pg_cluster: pg-citus0 ,pg_vip_address: 10.10.10.60/24 ,pg_seq: 1, pg_role: replica }
    10.10.10.52: { pg_group: 1, pg_cluster: pg-citus1 ,pg_vip_address: 10.10.10.61/24 ,pg_seq: 0, pg_role: primary }
    10.10.10.53: { pg_group: 1, pg_cluster: pg-citus1 ,pg_vip_address: 10.10.10.61/24 ,pg_seq: 1, pg_role: replica }
    10.10.10.54: { pg_group: 2, pg_cluster: pg-citus2 ,pg_vip_address: 10.10.10.62/24 ,pg_seq: 0, pg_role: primary }
    10.10.10.55: { pg_group: 2, pg_cluster: pg-citus2 ,pg_vip_address: 10.10.10.62/24 ,pg_seq: 1, pg_role: replica }
    10.10.10.56: { pg_group: 3, pg_cluster: pg-citus3 ,pg_vip_address: 10.10.10.63/24 ,pg_seq: 0, pg_role: primary }
    10.10.10.57: { pg_group: 3, pg_cluster: pg-citus3 ,pg_vip_address: 10.10.10.63/24 ,pg_seq: 1, pg_role: replica }
    10.10.10.58: { pg_group: 4, pg_cluster: pg-citus4 ,pg_vip_address: 10.10.10.64/24 ,pg_seq: 0, pg_role: primary }
    10.10.10.59: { pg_group: 4, pg_cluster: pg-citus4 ,pg_vip_address: 10.10.10.64/24 ,pg_seq: 1, pg_role: replica }
  vars:
    pg_mode: citus                            # pgsql cluster mode: citus
    pg_version: 17                            # Citus is not available for PG18 in v3.7.0
    pg_shard: pg-citus                        # citus shard name: pg-citus
    pg_primary_db: citus                      # primary database used by citus
    pg_vip_enabled: true                      # enable vip for citus cluster
    pg_vip_interface: eth1                    # vip interface for all members
    pg_dbsu_password: DBUser.Postgres         # enable dbsu password access for citus
    pg_extensions: [ citus, postgis, pgvector, topn, pg_cron, hll ]  # install these extensions
    pg_libs: 'citus, pg_cron, pg_stat_statements' # citus will be added by patroni automatically
    pg_users: [{ name: dbuser_citus ,password: DBUser.Citus ,pgbouncer: true ,roles: [ dbrole_admin ]    }]
    pg_databases: [{ name: citus ,owner: dbuser_citus ,extensions: [ citus, vector, topn, pg_cron, hll ] }]
    pg_parameters:
      cron.database_name: citus
      citus.node_conninfo: 'sslrootcert=/pg/cert/ca.crt sslmode=verify-full'
    pg_hba_rules:
      - { user: 'all' ,db: all  ,addr: 127.0.0.1/32  ,auth: ssl   ,title: 'all user ssl access from localhost' }
      - { user: 'all' ,db: all  ,addr: intra         ,auth: ssl   ,title: 'all user ssl access from intranet'  }

We’ll cover a range of advanced topics in subsequent tutorials:

  • Read-write separation
  • Failover handling
  • Consistent backup and restore
  • Advanced monitoring and troubleshooting
  • Connection pool

16.3 - Babelfish

MS SQL Server Wire compatibility on PostgreSQL

Pigsty allows users to create a Microsoft SQL Server compatible PostgreSQL cluster using Babelfish and WiltonDB!

  • Babelfish: An open-source MSSQL (Microsoft SQL Server) compatibility extension Open Sourced by AWS
  • WiltonDB: A PostgreSQL kernel distribution focusing on integrating Babelfish

Babelfish is a PostgreSQL extension, but it works on a slightly modified PostgreSQL kernel Fork, WiltonDB provides compiled kernel binaries and extension binary packages on EL/Ubuntu systems.

Pigsty can replace the native PostgreSQL kernel with WiltonDB, providing an out-of-the-box MSSQL compatible cluster along with all the supported by common PostgreSQL clusters, such as HA, PITR, IaC, monitoring, etc.

WiltonDB is very similar to PostgreSQL 15, but it can not use vanilla PostgreSQL extensions directly. WiltonDB has several re-compiled extensions such as system_stats, pg_hint_plan and tds_fdw.

The cluster will listen on the default PostgreSQL port and the default MSSQL 1433 port, providing MSSQL services via the TDS WireProtocol on this port. You can connect to the MSSQL service provided by Pigsty using any MSSQL client, such as SQL Server Management Studio, or using the sqlcmd command-line tool.


Get Started

install Pigsty’s with the mssql config template.

curl -fsSL https://repo.pigsty.io/get | bash -s v3.7.0; cd ~/pigsty;
./configure -c mssql     # Use mssql (babelfish) template
./install.yml            # install everything with pigsty

For production deployments, make sure to modify the password parameters in the pigsty.yml config before running the install playbook.


Notes

When installing and deploying the MSSQL module, please pay special attention to the following points:

  • WiltonDB is available on EL (7/8/9) and Ubuntu (20.04/22.04) but not available on Debian systems.
  • WiltonDB is currently compiled based on PostgreSQL 15, so you need to specify pg_version: 15.
  • On EL systems, the wiltondb binary is installed by default in the /usr/bin/ directory, while on Ubuntu systems, it is installed in the /usr/lib/postgresql/15/bin/ directory, which is different from the official PostgreSQL binary location.
  • In WiltonDB compatibility mode, the HBA password authentication rule needs to use md5 instead of scram-sha-256. Therefore, you need to override Pigsty’s default HBA rule set and insert the md5 authentication rule required by SQL Server before the dbrole_readonly wildcard authentication rule.
  • WiltonDB can only be enabled for a primary database, and you should designate a user as the Babelfish superuser, allowing Babelfish to create databases and users. The default is mssql and dbuser_myssql. If you change this, you should also modify the user in files/mssql.sql.
  • The WiltonDB TDS cable protocol compatibility plugin babelfishpg_tds needs to be enabled in shared_preload_libraries.
  • After enabling the WiltonDB extension, it listens on the default MSSQL port 1433. You can override Pigsty’s default service definitions to redirect the primary and replica services to port 1433 instead of the 5432 / 6432ports.

The following parameters need to be configured for the MSSQL database cluster:

pg-meta:
  hosts:
    10.10.10.10: { pg_seq: 1, pg_role: primary }
  vars:
    pg_cluster: pg-meta
    pg_users:
      - {name: dbuser_mssql ,password: DBUser.MSSQL ,superuser: true, pgbouncer: true ,roles: [dbrole_admin], comment: superuser & owner for babelfish  }
    pg_databases:
      - name: mssql
        baseline: mssql.sql
        extensions: [uuid-ossp, babelfishpg_common, babelfishpg_tsql, babelfishpg_tds, babelfishpg_money, pg_hint_plan, system_stats, tds_fdw]
        owner: dbuser_mssql
        parameters: { 'babelfishpg_tsql.migration_mode' : 'multi-db' }
        comment: babelfish cluster, a MSSQL compatible pg cluster
    node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ] # make a full backup every 1am

    # Babelfish / WiltonDB Ad Hoc Settings
    pg_mode: mssql                     # Microsoft SQL Server Compatible Mode
    pg_version: 15
    pg_packages: [ wiltondb, pgsql-common, sqlcmd ]
    pg_libs: 'babelfishpg_tds, pg_stat_statements, auto_explain' # add timescaledb to shared_preload_libraries
    pg_default_hba_rules: # overwrite default HBA rules for babelfish cluster
      - { user: '${dbsu}'    ,db: all         ,addr: local     ,auth: ident ,title: 'dbsu access via local os user ident' }
      - { user: '${dbsu}'    ,db: replication ,addr: local     ,auth: ident ,title: 'dbsu replication from local os ident' }
      - { user: '${repl}'    ,db: replication ,addr: localhost ,auth: pwd   ,title: 'replicator replication from localhost' }
      - { user: '${repl}'    ,db: replication ,addr: intra     ,auth: pwd   ,title: 'replicator replication from intranet' }
      - { user: '${repl}'    ,db: postgres    ,addr: intra     ,auth: pwd   ,title: 'replicator postgres db from intranet' }
      - { user: '${monitor}' ,db: all         ,addr: localhost ,auth: pwd   ,title: 'monitor from localhost with password' }
      - { user: '${monitor}' ,db: all         ,addr: infra     ,auth: pwd   ,title: 'monitor from infra host with password' }
      - { user: '${admin}'   ,db: all         ,addr: infra     ,auth: ssl   ,title: 'admin @ infra nodes with pwd & ssl' }
      - { user: '${admin}'   ,db: all         ,addr: world     ,auth: ssl   ,title: 'admin @ everywhere with ssl & pwd' }
      - { user: dbuser_mssql ,db: mssql       ,addr: intra     ,auth: md5   ,title: 'allow mssql dbsu intranet access' } # <--- use md5 auth method for mssql user
      - { user: '+dbrole_readonly',db: all    ,addr: localhost ,auth: pwd   ,title: 'pgbouncer read/write via local socket' }
      - { user: '+dbrole_readonly',db: all    ,addr: intra     ,auth: pwd   ,title: 'read/write biz user via password' }
      - { user: '+dbrole_offline' ,db: all    ,addr: intra     ,auth: pwd   ,title: 'allow etl offline tasks from intranet' }
    pg_default_services: # route primary & replica service to mssql port 1433
      - { name: primary ,port: 5433 ,dest: 1433  ,check: /primary   ,selector: "[]" }
      - { name: replica ,port: 5434 ,dest: 1433  ,check: /read-only ,selector: "[]" , backup: "[? pg_role == `primary` || pg_role == `offline` ]" }
      - { name: default ,port: 5436 ,dest: postgres ,check: /primary   ,selector: "[]" }
      - { name: offline ,port: 5438 ,dest: postgres ,check: /replica   ,selector: "[? pg_role == `offline` || pg_offline_query ]" , backup: "[? pg_role == `replica` && !pg_offline_query]" }

You can define business databases & users in the pg_databases and pg_users section:

#----------------------------------#
# pgsql (singleton on current node)
#----------------------------------#
# this is an example single-node postgres cluster with postgis & timescaledb installed, with one biz database & two biz users
pg-meta:
  hosts:
    10.10.10.10: { pg_seq: 1, pg_role: primary } # <---- primary instance with read-write capability
  vars:
    pg_cluster: pg-test
    pg_users:                           # create MSSQL superuser
      - {name: dbuser_mssql ,password: DBUser.MSSQL ,superuser: true, pgbouncer: true ,roles: [dbrole_admin], comment: superuser & owner for babelfish  }
    pg_primary_db: mssql                # use `mssql` as the primary sql server database
    pg_databases:
      - name: mssql
        baseline: mssql.sql             # init babelfish database & user
        extensions:
          - { name: uuid-ossp          }
          - { name: babelfishpg_common }
          - { name: babelfishpg_tsql   }
          - { name: babelfishpg_tds    }
          - { name: babelfishpg_money  }
          - { name: pg_hint_plan       }
          - { name: system_stats       }
          - { name: tds_fdw            }
        owner: dbuser_mssql
        parameters: { 'babelfishpg_tsql.migration_mode' : 'multi-db' }
        comment: babelfish cluster, a MSSQL compatible pg cluster

Client Access

You can use any SQL Server compatible client tool to access this database cluster.

Microsoft provides sqlcmd as the official command-line tool.

Besides, they have a go version cli tool: go-sqlcmd

Install go-sqlcmd:

curl -LO https://github.com/microsoft/go-sqlcmd/releases/download/v1.4.0/sqlcmd-v1.4.0-linux-amd64.tar.bz2
tar xjvf sqlcmd-v1.4.0-linux-amd64.tar.bz2
sudo mv sqlcmd* /usr/bin/

Get started with go-sqlcmd

$ sqlcmd -S 10.10.10.10,1433 -U dbuser_mssql -P DBUser.MSSQL
1> select @@version
2> go
version
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Babelfish for PostgreSQL with SQL Server Compatibility - 12.0.2000.8
Oct 22 2023 17:48:32
Copyright (c) Amazon Web Services
PostgreSQL 15.4 (EL 1:15.4.wiltondb3.3_2-2.el8) on x86_64-redhat-linux-gnu (Babelfish 3.3.0)

(1 row affected)

You can route service traffic to MSSQL 1433 port instead of 5433/5434:

# route 5433 on all members to 1433 on primary
sqlcmd -S 10.10.10.11,5433 -U dbuser_mssql -P DBUser.MSSQL

# route 5434 on all members to 1433 on replicas
sqlcmd -S 10.10.10.11,5434 -U dbuser_mssql -P DBUser.MSSQL

Install

If you have the Internet access, you can add the WiltonDB repository to the node and install it as a node package directly:

node_repo_modules: local,node,pgsql,mssql
node_packages: [ wiltondb ]

Install wiltondb with the following command:

./node.yml -t node_repo,node_pkg

It’s OK to install vanilla PostgreSQL and WiltonDB on the same node, but you can only run one of them at a time, and this is not recommended for production environments.


Extensions

Most of the PGSQL module’s extensions (non-SQL class) cannot be used directly on the WiltonDB core of the MSSQL module and need to be recompiled.

WiltonDB currently comes with the following extension plugins:

Name Version Comment
dblink 1.2 connect to other PostgreSQL databases from within a database
adminpack 2.1 administrative functions for PostgreSQL
dict_int 1.0 text search dictionary template for integers
intagg 1.1 integer aggregator and enumerator (obsolete)
dict_xsyn 1.0 text search dictionary template for extended synonym processing
amcheck 1.3 functions for verifying relation integrity
autoinc 1.0 functions for autoincrementing fields
bloom 1.0 bloom access method - signature file based index
fuzzystrmatch 1.1 determine similarities and distance between strings
intarray 1.5 functions, operators, and index support for 1-D arrays of integers
btree_gin 1.3 support for indexing common datatypes in GIN
btree_gist 1.7 support for indexing common datatypes in GiST
hstore 1.8 data type for storing sets of (key, value) pairs
hstore_plperl 1.0 transform between hstore and plperl
isn 1.2 data types for international product numbering standards
hstore_plperlu 1.0 transform between hstore and plperlu
jsonb_plperl 1.0 transform between jsonb and plperl
citext 1.6 data type for case-insensitive character strings
jsonb_plperlu 1.0 transform between jsonb and plperlu
jsonb_plpython3u 1.0 transform between jsonb and plpython3u
cube 1.5 data type for multidimensional cubes
hstore_plpython3u 1.0 transform between hstore and plpython3u
earthdistance 1.1 calculate great-circle distances on the surface of the Earth
lo 1.1 Large Object maintenance
file_fdw 1.0 foreign-data wrapper for flat file access
insert_username 1.0 functions for tracking who changed a table
ltree 1.2 data type for hierarchical tree-like structures
ltree_plpython3u 1.0 transform between ltree and plpython3u
pg_walinspect 1.0 functions to inspect contents of PostgreSQL Write-Ahead Log
moddatetime 1.0 functions for tracking last modification time
old_snapshot 1.0 utilities in support of old_snapshot_threshold
pgcrypto 1.3 cryptographic functions
pgrowlocks 1.2 show row-level locking information
pageinspect 1.11 inspect the contents of database pages at a low level
pg_surgery 1.0 extension to perform surgery on a damaged relation
seg 1.4 data type for representing line segments or floating-point intervals
pgstattuple 1.5 show tuple-level statistics
pg_buffercache 1.3 examine the shared buffer cache
pg_freespacemap 1.2 examine the free space map (FSM)
postgres_fdw 1.1 foreign-data wrapper for remote PostgreSQL servers
pg_prewarm 1.2 prewarm relation data
tcn 1.0 Triggered change notifications
pg_trgm 1.6 text similarity measurement and index searching based on trigrams
xml2 1.1 XPath querying and XSLT
refint 1.0 functions for implementing referential integrity (obsolete)
pg_visibility 1.2 examine the visibility map (VM) and page-level visibility info
pg_stat_statements 1.10 track planning and execution statistics of all SQL statements executed
sslinfo 1.2 information about SSL certificates
tablefunc 1.0 functions that manipulate whole tables, including crosstab
tsm_system_rows 1.0 TABLESAMPLE method which accepts number of rows as a limit
tsm_system_time 1.0 TABLESAMPLE method which accepts time in milliseconds as a limit
unaccent 1.1 text search dictionary that removes accents
uuid-ossp 1.1 generate universally unique identifiers (UUIDs)
plpgsql 1.0 PL/pgSQL procedural language
babelfishpg_money 1.1.0 babelfishpg_money
system_stats 2.0 EnterpriseDB system statistics for PostgreSQL
tds_fdw 2.0.3 Foreign data wrapper for querying a TDS database (Sybase or Microsoft SQL Server)
babelfishpg_common 3.3.3 Transact SQL Datatype Support
babelfishpg_tds 1.0.0 TDS protocol extension
pg_hint_plan 1.5.1
babelfishpg_tsql 3.3.1 Transact SQL compatibility

16.4 - IvorySQL

PostgreSQL fork with oracle (grammar) compatibility

IvorySQL is an open-source “Oracle-compatible” PostgreSQL kernel, developed by HighGo, licensed under Apache 2.0.

The Oracle compatibility here refers to compatibility at the PL/SQL, syntax, built-in functions, data types, system views, MERGE, and GUC parameter levels. It’s not a wire protocol compatibility like Babelfish, openHalo, or FerretDB that allows using the original client drivers. Users still need to use PostgreSQL client tools to access IvorySQL, but can use Oracle-compatible syntax.

Currently, IvorySQL’s latest version 5.0 maintains compatibility with PostgreSQL’s latest minor version 18.0, and provides binary RPM/DEB packages for mainstream Linux distributions. Pigsty offers the option to replace the native PostgreSQL with the IvorySQL kernel in PG RDS.


Quick Start

Use the standard procedure to install Pigsty with the ivory configuration template:

curl -fsSL https://repo.pigsty.io/get | bash -s v3.7.0; cd ~/pigsty;
./configure -c ivory     # Use IvorySQL configuration template
./install.yml            # Run installation playbook

For production deployments, you should edit the auto-generated pigsty.yml configuration file to modify parameters like passwords before executing ./install.yml for deployment.

The latest IvorySQL 5.0 is equivalent to PostgreSQL 18.0 Any client tool compatible with PostgreSQL’s wire protocol can access IvorySQL clusters.

By default, you can use a PostgreSQL client to access through the alternative 1521 port, which enables Oracle compatibility mode by default.


Configuration Instructions

To use the IvorySQL kernel in Pigsty, modify the following four configuration parameters:

It’s that simple — just add these four lines to the global variables in the configuration file, and Pigsty will replace the native PostgreSQL kernel with IvorySQL:

pg_mode: ivory                           # IvorySQL compatibility mode, uses IvorySQL binaries
pg_packages: [ ivorysql, pgsql-common ]  # Install ivorysql, replacing pgsql-main kernel
pg_libs: 'liboracle_parser, pg_stat_statements, auto_explain'  # Load Oracle compatibility extensions
repo_extra_packages: [ ivorysql ]        # Download ivorysql packages

IvorySQL also provides a series of new GUC parameters that can be specified in pg_parameters.


Extensions

Most of the PGSQL modules’ extension (non-SQL classes) cannot be used directly on the IvorySQL kernel. If you need to use them, you need to recompile and install from source code for the new kernel.


Caveats

  • The IvorySQL software package is located in the pigsty-infra repository, not in pigsty-pgsql or pigsty-ivory repositories.
  • Pigsty does not assume any warranty for using the IvorySQL kernel, and any issues or requests should be addressed to the manufacturer.

16.5 - Percona

Percona Postgres Distribution with TDE support

Percona Postgres is a patched Postgres kernel with pg_tde (Transparent Data Encryption) extension.

It is compatible with PostgreSQL 18.1, and available on all supported platforms in Pigsty.


Get Started

install Pigsty’s with the pgtde config template.

curl -fsSL https://repo.pigsty.io/get | bash -s v3.7.0; cd ~/pigsty;
./configure -c pgtde     # use percona postgres kernel
./install.yml            # setup everything with pigsty

Configure

The following parameters need to be tuned to deploy a percona cluster:

pg-meta:
  hosts:
    10.10.10.10: { pg_seq: 1, pg_role: primary }
  vars:
    pg_cluster: pg-meta
    pg_users:
      - { name: dbuser_meta ,password: DBUser.Meta   ,pgbouncer: true ,roles: [dbrole_admin   ] ,comment: pigsty admin user }
      - { name: dbuser_view ,password: DBUser.Viewer ,pgbouncer: true ,roles: [dbrole_readonly] ,comment: read-only viewer  }
    pg_databases:
      - name: meta
        baseline: cmdb.sql
        comment: pigsty tde database
        schemas: [pigsty]
        extensions: [ vector, postgis, pg_tde ,pgaudit, { name: pg_stat_monitor, schema: monitor } ]
    pg_hba_rules:
      - { user: dbuser_view , db: all ,addr: infra ,auth: pwd ,title: 'allow grafana dashboard access cmdb from infra nodes' }
    node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ] # make a full backup every 1am

    # Percona PostgreSQL TDE Ad Hoc Settings
    pg_packages: [ percona-main, pgsql-common ]  # install percona postgres packages
    pg_libs: 'pg_tde, pgaudit, pg_stat_statements, pg_stat_monitor, auto_explain'

Extensions

Percona Postgres has 80 available extensions, including pg_tde, pgvector, postgis, pgaudit, set_user, pg_stat_monitor,….

name version comment
hstore_plperlu 1.0 transform between hstore and plperlu
jsonb_plperl 1.0 transform between jsonb and plperl
intagg 1.1 integer aggregator and enumerator (obsolete)
pltcl 1.0 PL/Tcl procedural language
isn 1.3 data types for international product numbering standards
pgstattuple 1.5 show tuple-level statistics
postgis_topology-3 3.5.4 PostGIS topology spatial types and functions
postgis_raster 3.5.4 PostGIS raster types and functions
tsm_system_rows 1.0 TABLESAMPLE method which accepts number of rows as a limit
lo 1.2 Large Object maintenance
hstore_plperl 1.0 transform between hstore and plperl
ltree 1.3 data type for hierarchical tree-like structures
postgis_raster-3 3.5.4 PostGIS raster types and functions
postgis_topology 3.5.4 PostGIS topology spatial types and functions
pgrowlocks 1.2 show row-level locking information
address_standardizer_data_us-3 3.5.4 Address Standardizer US dataset example
uuid-ossp 1.1 generate universally unique identifiers (UUIDs)
postgis-3 3.5.4 PostGIS geometry and geography spatial types and functions
hstore_plpython3u 1.0 transform between hstore and plpython3u
postgis 3.5.4 PostGIS geometry and geography spatial types and functions
set_user 4.2.0 similar to SET ROLE but with added logging
postgis_tiger_geocoder-3 3.5.4 PostGIS tiger geocoder and reverse geocoder
jsonb_plperlu 1.0 transform between jsonb and plperlu
pg_surgery 1.0 extension to perform surgery on a damaged relation
xml2 1.2 XPath querying and XSLT
pg_stat_monitor 2.3 The pg_stat_monitor is a PostgreSQL Query Performance Monitoring tool, based on PostgreSQL contrib module pg_stat_statements. pg_stat_monitor provides aggregated statistics, client information, plan details including plan, and histogram information.
pg_tde 2.1 pg_tde access method
plpgsql 1.0 PL/pgSQL procedural language
address_standardizer-3 3.5.4 Used to parse an address into constituent elements. Generally used to support geocoding address normalization step.
tablefunc 1.0 functions that manipulate whole tables, including crosstab
hstore 1.8 data type for storing sets of (key, value) pairs
vector 0.8.1 vector data type and ivfflat and hnsw access methods
postgis_tiger_geocoder 3.5.4 PostGIS tiger geocoder and reverse geocoder
dblink 1.2 connect to other PostgreSQL databases from within a database
pltclu 1.0 PL/TclU untrusted procedural language
pg_trgm 1.6 text similarity measurement and index searching based on trigrams
sslinfo 1.2 information about SSL certificates
pg_stat_statements 1.12 track planning and execution statistics of all SQL statements executed
bool_plperlu 1.0 transform between bool and plperlu
cube 1.5 data type for multidimensional cubes
ltree_plpython3u 1.0 transform between ltree and plpython3u
amcheck 1.5 functions for verifying relation integrity
postgis_sfcgal 3.5.4 PostGIS SFCGAL functions
plpython3u 1.0 PL/Python3U untrusted procedural language
tsm_system_time 1.0 TABLESAMPLE method which accepts time in milliseconds as a limit
intarray 1.5 functions, operators, and index support for 1-D arrays of integers
btree_gist 1.8 support for indexing common datatypes in GiST
plperlu 1.0 PL/PerlU untrusted procedural language
fuzzystrmatch 1.2 determine similarities and distance between strings
bool_plperl 1.0 transform between bool and plperl
btree_gin 1.3 support for indexing common datatypes in GIN
pg_prewarm 1.2 prewarm relation data
pg_repack 1.5.3 Reorganize tables in PostgreSQL databases with minimal locks
citext 1.8 data type for case-insensitive character strings
pgcrypto 1.4 cryptographic functions
moddatetime 1.0 functions for tracking last modification time
plperl 1.0 PL/Perl procedural language
seg 1.4 data type for representing line segments or floating-point intervals
earthdistance 1.2 calculate great-circle distances on the surface of the Earth
unaccent 1.1 text search dictionary that removes accents
postgres_fdw 1.2 foreign-data wrapper for remote PostgreSQL servers
pg_logicalinspect 1.0 functions to inspect logical decoding components
tcn 1.0 Triggered change notifications
bloom 1.0 bloom access method - signature file based index
dict_int 1.0 text search dictionary template for integers
autoinc 1.0 functions for autoincrementing fields
address_standardizer_data_us 3.5.4 Address Standardizer US dataset example
postgis_sfcgal-3 3.5.4 PostGIS SFCGAL functions
jsonb_plpython3u 1.0 transform between jsonb and plpython3u
file_fdw 1.0 foreign-data wrapper for flat file access
pgaudit 18.0 provides auditing functionality
dict_xsyn 1.0 text search dictionary template for extended synonym processing
pg_walinspect 1.1 functions to inspect contents of PostgreSQL Write-Ahead Log
pg_buffercache 1.6 examine the shared buffer cache
refint 1.0 functions for implementing referential integrity (obsolete)
pg_freespacemap 1.3 examine the free space map (FSM)
insert_username 1.0 functions for tracking who changed a table
address_standardizer 3.5.4 Used to parse an address into constituent elements. Generally used to support geocoding address normalization step.
pg_visibility 1.2 examine the visibility map (VM) and page-level visibility info
pageinspect 1.13 inspect the contents of database pages at a low level

16.6 - PolarDB

PolarDB for PostgreSQL, with aurora flavor RAC

PolarDB is an aurora RAC flavor “cloud native” database system developed & open-sourced by Aliyun.

The latest version is v15.15.5.0, compatible with PostgreSQL 15, and available on all linux distributions supported by Pigsty.


Get Started

install Pigsty’s with the polar config template.

curl -fsSL https://repo.pigsty.io/get | bash -s v3.7.0; cd ~/pigsty;
./configure -c polar     # Use polar (PolarDB) template
./install.yml            # Run Deployment Playbook

Configure

The following parameters need to be tuned to deploy a PolarDB cluster:

pg-meta:
  hosts:
    10.10.10.10: { pg_seq: 1, pg_role: primary }
  vars:
    pg_cluster: pg-meta
    pg_users:
      - {name: dbuser_meta ,password: DBUser.Meta   ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: pigsty admin user }
      - {name: dbuser_view ,password: DBUser.Viewer ,pgbouncer: true ,roles: [dbrole_readonly] ,comment: read-only viewer for meta database }
    pg_databases:
      - {name: meta ,baseline: cmdb.sql ,comment: pigsty meta database ,schemas: [pigsty]}
    pg_hba_rules:
      - {user: dbuser_view , db: all ,addr: infra ,auth: pwd ,title: 'allow grafana dashboard access cmdb from infra nodes'}
    node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ] # make a full backup every 1am

    # PolarDB Ad Hoc Settings
    pg_version: 15                            # PolarDB PG is based on PG 15
    pg_mode: polar                            # PolarDB PG Compatible mode
    pg_packages: [ polardb, pgsql-common ]    # Replace PG kernel with PolarDB kernel
    pg_exporter_exclude_database: 'template0,template1,postgres,polardb_admin'
    pg_default_roles:                         # PolarDB require replicator as superuser
      - { name: dbrole_readonly  ,login: false ,comment: role for global read-only access     }
      - { name: dbrole_offline   ,login: false ,comment: role for restricted read-only access }
      - { name: dbrole_readwrite ,login: false ,roles: [dbrole_readonly] ,comment: role for global read-write access }
      - { name: dbrole_admin     ,login: false ,roles: [pg_monitor, dbrole_readwrite] ,comment: role for object creation }
      - { name: postgres     ,superuser: true  ,comment: system superuser }
      - { name: replicator   ,superuser: true  ,replication: true ,roles: [pg_monitor, dbrole_readonly] ,comment: system replicator } # <- superuser is required for replication
      - { name: dbuser_dba   ,superuser: true  ,roles: [dbrole_admin]  ,pgbouncer: true ,pool_mode: session, pool_connlimit: 16 ,comment: pgsql admin user }
      - { name: dbuser_monitor ,roles: [pg_monitor] ,pgbouncer: true ,parameters: {log_min_duration_statement: 1000 } ,pool_mode: session ,pool_connlimit: 8 ,comment: pgsql monitor user }

Client Access

PolarDB for PostgreSQL is essentially equivalent to PostgreSQL 15, and any client tools compatible with the PostgreSQL wire protocol can access the PolarDB cluster.


Extensions

Most of the PGSQL module’s extension (non pure-SQL) cannot be used directly on the PolarDB kernel. If you need to use them, you need to recompile and install from source code for the new kernel.

Currently, the PolarDB kernel comes with the following 61 extension plugins. In addition to Contrib extensions, the additional extensions provided include:

name Version comment
adminpack 2.1 administrative functions for PostgreSQL
amcheck 1.3 functions for verifying relation integrity
autoinc 1.0 functions for autoincrementing fields
bloom 1.0 bloom access method - signature file based index
bool_plperl 1.0 transform between bool and plperl
bool_plperlu 1.0 transform between bool and plperlu
btree_gin 1.3 support for indexing common datatypes in GIN
btree_gist 1.7 support for indexing common datatypes in GiST
citext 1.6 data type for case-insensitive character strings
cube 1.5 data type for multidimensional cubes
dblink 1.2 connect to other PostgreSQL databases from within a database
dict_int 1.0 text search dictionary template for integers
dict_xsyn 1.0 text search dictionary template for extended synonym processing
earthdistance 1.1 calculate great-circle distances on the surface of the Earth
file_fdw 1.0 foreign-data wrapper for flat file access
fuzzystrmatch 1.1 determine similarities and distance between strings
hll 2.18 type for storing hyperloglog data
hstore 1.8 data type for storing sets of (key, value) pairs
hstore_plperl 1.0 transform between hstore and plperl
hstore_plperlu 1.0 transform between hstore and plperlu
hstore_plpython3u 1.0 transform between hstore and plpython3u
hypopg 1.3.1 Hypothetical indexes for PostgreSQL
insert_username 1.0 functions for tracking who changed a table
intagg 1.1 integer aggregator and enumerator (obsolete)
intarray 1.5 functions, operators, and index support for 1-D arrays of integers
isn 1.2 data types for international product numbering standards
jsonb_plperl 1.0 transform between jsonb and plperl
jsonb_plperlu 1.0 transform between jsonb and plperlu
jsonb_plpython3u 1.0 transform between jsonb and plpython3u
lo 1.1 Large Object maintenance
log_fdw 1.4 foreign-data wrapper for Postgres log file access
ltree 1.2 data type for hierarchical tree-like structures
ltree_plpython3u 1.0 transform between ltree and plpython3u
moddatetime 1.0 functions for tracking last modification time
old_snapshot 1.0 utilities in support of old_snapshot_threshold
pageinspect 1.11 inspect the contents of database pages at a low level
pase 0.0.1 ant ai similarity search
pg_bigm 1.2 text similarity measurement and index searching based on bigrams
pg_buffercache 1.4 examine the shared buffer cache
pg_freespacemap 1.2 examine the free space map (FSM)
pg_jieba 1.1.0 a parser for full-text search of Chinese
pg_prewarm 1.2 prewarm relation data
pg_repack 1.5.1-1 Reorganize tables in PostgreSQL databases with minimal locks
pg_stat_statements 1.10 track planning and execution statistics of all SQL statements executed
pg_surgery 1.0 extension to perform surgery on a damaged relation
pg_trgm 1.6 text similarity measurement and index searching based on trigrams
pg_visibility 1.2 examine the visibility map (VM) and page-level visibility info
pg_walinspect 1.0 functions to inspect contents of PostgreSQL Write-Ahead Log
pgcrypto 1.3 cryptographic functions
pgrowlocks 1.2 show row-level locking information
pgstattuple 1.5 show tuple-level statistics
plperl 1.0 PL/Perl procedural language
plperlu 1.0 PL/PerlU untrusted procedural language
plpgsql 1.0 PL/pgSQL procedural language
plpython3u 1.0 PL/Python3U untrusted procedural language
pltcl 1.0 PL/Tcl procedural language
pltclu 1.0 PL/TclU untrusted procedural language
polar_audit 1.0 provides auditing functionality
polar_feature_utils 1.0 PolarDB feature utilization
polar_io_stat 1.0 polar io stat in multi dimension
polar_login_history 1.0 record user login information
polar_masking 1.0.0 provides data masking for polardb
polar_monitor 1.0 monitor functions for PolarDB
polar_monitor_preload 1.0 examine the polardb information
polar_parameter_manager 1.1 Extension to select parameters for manger.
polar_password_policy 1.0 create password policies and check user passwords based on the policies
polar_proxy_utils 1.0 Extension to provide operations about proxy.
polar_resource_manager 1.0 a background process that forcibly frees user session process memory
polar_smgrperf 1.0 smgr perf test extension
polar_sql_mapping 1.0 Record error sqls and mapping them to correct one
polar_stat_env 1.0 env stat functions for PolarDB
polar_vfs 1.0 polar virtual file system for different storage
polar_worker 1.0 polar_worker
postgres_fdw 1.1 foreign-data wrapper for remote PostgreSQL servers
refint 1.0 functions for implementing referential integrity (obsolete)
roaringbitmap 0.5 support for Roaring Bitmaps
seg 1.4 data type for representing line segments or floating-point intervals
sslinfo 1.2 information about SSL certificates
tablefunc 1.0 functions that manipulate whole tables, including crosstab
tcn 1.0 Triggered change notifications
tsm_system_rows 1.0 TABLESAMPLE method which accepts number of rows as a limit
tsm_system_time 1.0 TABLESAMPLE method which accepts time in milliseconds as a limit
unaccent 1.1 text search dictionary that removes accents
uuid-ossp 1.1 generate universally unique identifiers (UUIDs)
vector 0.6.2 vector data type and ivfflat and hnsw access methods
xml2 1.1 XPath querying and XSLT

PolarDB for Oracle

There’s 2nd fork of PolarDB, which is PolarDB for Oracle, which is not open source.

Pigsty Pro has support for Running PolarDB for Oracle as RDS.

16.7 - OrioleDB

Next Gen OLTP engine for PostgreSQL

OrioleDB is a PostgreSQL storage engine extension that claims to deliver 4x OLTP performance without the xid wraparound & table bloat, and “cloud native” (data on s3) capabilities.

The latest version of OrioleDB is based on a Patched PostgreSQL 17.0 with an additional extension

You can run OrioleDB as RDS with pigsty, it is compatible with PG 17 and available on all supported Linux platforms. The latest version is beta12 over patchset 17_11.


Get Started

Follow the Pigsty standard installation and use the oriole config template.

curl -fsSL https://repo.pigsty.io/get | bash -s v3.7.0; cd ~/pigsty;
./configure -c oriole    # Use the OrioleDB configuration template
./install.yml            # Install Pigsty with OrioleDB

For production deployments, make sure to modify the password parameters in the pigsty.yml config before running the install playbook.


Configuration

pg-meta:
  hosts:
    10.10.10.10: { pg_seq: 1, pg_role: primary }
  vars:
    pg_cluster: pg-meta
    pg_users:
      - {name: dbuser_meta ,password: DBUser.Meta   ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: pigsty admin user }
      - {name: dbuser_view ,password: DBUser.Viewer ,pgbouncer: true ,roles: [dbrole_readonly] ,comment: read-only viewer for meta database }
    pg_databases:
      - {name: meta ,baseline: cmdb.sql ,comment: pigsty meta database ,schemas: [pigsty], extensions: [orioledb]}
    pg_hba_rules:
      - {user: dbuser_view , db: all ,addr: infra ,auth: pwd ,title: 'allow grafana dashboard access cmdb from infra nodes'}
    node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ] # make a full backup every 1am

    # OrioleDB Ad Hoc Settings
    pg_mode: oriole                                         # oriole compatible mode
    pg_packages: [ orioledb, pgsql-common ]                 # install OrioleDB kernel
    pg_libs: 'orioledb, pg_stat_statements, auto_explain'   # Load OrioleDB Extension

Usage

To use OrioleDB, you need to install the orioledb_17 and oriolepg_17 packages (currently only available as RPMs).

Initialize TPC-B-like tables with 100 warehouses using pgbench:

pgbench -is 100 meta
pgbench -nv -P1 -c10 -S -T1000 meta
pgbench -nv -P1 -c50 -S -T1000 meta
pgbench -nv -P1 -c10    -T1000 meta
pgbench -nv -P1 -c50    -T1000 meta

Next, you can rebuild these tables using the orioledb storage engine and observe the performance differences:

-- Create OrioleDB tables
CREATE TABLE pgbench_accounts_o (LIKE pgbench_accounts INCLUDING ALL) USING orioledb;
CREATE TABLE pgbench_branches_o (LIKE pgbench_branches INCLUDING ALL) USING orioledb;
CREATE TABLE pgbench_history_o (LIKE pgbench_history INCLUDING ALL) USING orioledb;
CREATE TABLE pgbench_tellers_o (LIKE pgbench_tellers INCLUDING ALL) USING orioledb;

-- Copy data from regular tables to OrioleDB tables
INSERT INTO pgbench_accounts_o SELECT * FROM pgbench_accounts;
INSERT INTO pgbench_branches_o SELECT * FROM pgbench_branches;
INSERT INTO pgbench_history_o SELECT  * FROM pgbench_history;
INSERT INTO pgbench_tellers_o SELECT * FROM pgbench_tellers;

-- Drop original tables and rename OrioleDB tables
DROP TABLE pgbench_accounts, pgbench_branches, pgbench_history, pgbench_tellers;
ALTER TABLE pgbench_accounts_o RENAME TO pgbench_accounts;
ALTER TABLE pgbench_branches_o RENAME TO pgbench_branches;
ALTER TABLE pgbench_history_o RENAME TO pgbench_history;
ALTER TABLE pgbench_tellers_o RENAME TO pgbench_tellers;

16.8 - OpenHalo

MySQL Compatible Postgres 14 Fork

OpenHalo is an open-source PostgreSQL kernel that provides MySQL wire protocol compatibility.

OpenHalo is based on PostgreSQL 14.10 kernel version and provides wire protocol compatibility with MySQL 5.7.32-log / 8.0 version.

Pigsty provides deployment support for OpenHalo on all supported Linux platforms.


Get Started

Use Pigsty’s standard installation process with the mysql configuration template.

curl -fsSL https://repo.pigsty.io/get | bash -s v3.7.0; cd ~/pigsty;
./configure -c mysql    # Use MySQL (openHalo) configuration template
./install.yml           # Install, for production deployment please modify passwords in pigsty.yml first

For production deployment, please ensure to modify the password parameters in the pigsty.yml configuration file before running the installation playbook.


Configuration

pg-meta:
  hosts:
    10.10.10.10: { pg_seq: 1, pg_role: primary }
  vars:
    pg_cluster: pg-meta
    pg_users:
      - {name: dbuser_meta ,password: DBUser.Meta   ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: pigsty admin user }
      - {name: dbuser_view ,password: DBUser.Viewer ,pgbouncer: true ,roles: [dbrole_readonly] ,comment: read-only viewer for meta database }
    pg_databases:
      - {name: postgres, extensions: [aux_mysql]} # the mysql compatible database
      - {name: meta ,baseline: cmdb.sql ,comment: pigsty meta database ,schemas: [pigsty]}
    pg_hba_rules:
      - {user: dbuser_view , db: all ,addr: infra ,auth: pwd ,title: 'allow grafana dashboard access cmdb from infra nodes'}
    node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ] # make a full backup every 1am

    # OpenHalo Ad Hoc Setting
    pg_mode: mysql                    # MySQL Compatible Mode by HaloDB
    pg_version: 14                    # The current HaloDB is compatible with PG Major Version 14
    pg_packages: [ openhalodb, pgsql-common ]  # install openhalodb instead of postgresql kernel

Usage

When accessing MySQL, the actual connection uses the postgres database. Please note that the concept of “database” in MySQL actually corresponds to “Schema” in PostgreSQL. Therefore, use mysql actually uses the mysql Schema within the postgres database.

The usernames and passwords used for MySQL are the same as those in PostgreSQL. You can manage users and permissions using the standard PostgreSQL approach.

Client Access

OpenHalo provides MySQL wire protocol compatibility, listening on port 3306 by default, allowing direct connections from MySQL clients and drivers.

Pigsty’s conf/mysql configuration installs the mysql client tool by default.

You can access MySQL using the following command:

mysql -h 127.0.0.1 -u dbuser_dba

Currently, OpenHalo officially ensures that Navicat can access this MySQL port normally, but Intellij IDEA’s DataGrip access will result in errors.


Modification

The OpenHalo kernel installed by Pigsty is based on the HaloTech-Co-Ltd/openHalo kernel with minor modifications:

  • Changed the default database name from halo0root back to postgres
  • Removed the 1.0. prefix from the default version number, reverting to 14.10
  • Modified the default configuration file to enable MySQL compatibility and listen on port 3306 by default

Please note that Pigsty does not provide any warranty for using the OpenHalo kernel. Any issues or requirements encountered while using this kernel should be addressed with the original vendor.

16.9 - Cloudberry

Cloudberry and Greenplum, the MPP data warehouse

You can deploy and monitor Cloudberry clusters, which is a Greenplum fork.

To define a Greenplum cluster, you need to specify the following parameters:

Wait for 2.0 GA

We are waiting for the official release of Apache Cloudberry 2.0, so do not use it in production now


Install

To install cloudberry, you’ll have to enable the gpsql repo module:

./node.yml -t node_install  -e '{"node_repo_modules":"node,pgsql,gpsql","node_packages":["cloudberrydb"]}'

Configure

Set pg_mode = gpsql and the extra identity parameters pg_shard and gp_role.

#================================================================#
#                        GPSQL Clusters                          #
#================================================================#

#----------------------------------#
# cluster: mx-mdw (gp master)
#----------------------------------#
mx-mdw:
  hosts:
    10.10.10.10: { pg_seq: 1, pg_role: primary , nodename: mx-mdw-1 }
  vars:
    gp_role: master          # this cluster is used as greenplum master
    pg_shard: mx             # pgsql sharding name & gpsql deployment name
    pg_cluster: mx-mdw       # this master cluster name is mx-mdw
    pg_databases:
      - { name: matrixmgr , extensions: [ { name: matrixdbts } ] }
      - { name: meta }
    pg_users:
      - { name: meta , password: DBUser.Meta , pgbouncer: true }
      - { name: dbuser_monitor , password: DBUser.Monitor , roles: [ dbrole_readonly ], superuser: true }

    pgbouncer_enabled: true                # enable pgbouncer for greenplum master
    pgbouncer_exporter_enabled: false      # enable pgbouncer_exporter for greenplum master
    pg_exporter_params: 'host=127.0.0.1&sslmode=disable'  # use 127.0.0.1 as local monitor host

#----------------------------------#
# cluster: mx-sdw (gp master)
#----------------------------------#
mx-sdw:
  hosts:
    10.10.10.11:
      nodename: mx-sdw-1        # greenplum segment node
      pg_instances:             # greenplum segment instances
        6000: { pg_cluster: mx-seg1, pg_seq: 1, pg_role: primary , pg_exporter_port: 9633 }
        6001: { pg_cluster: mx-seg2, pg_seq: 2, pg_role: replica , pg_exporter_port: 9634 }
    10.10.10.12:
      nodename: mx-sdw-2
      pg_instances:
        6000: { pg_cluster: mx-seg2, pg_seq: 1, pg_role: primary , pg_exporter_port: 9633  }
        6001: { pg_cluster: mx-seg3, pg_seq: 2, pg_role: replica , pg_exporter_port: 9634  }
    10.10.10.13:
      nodename: mx-sdw-3
      pg_instances:
        6000: { pg_cluster: mx-seg3, pg_seq: 1, pg_role: primary , pg_exporter_port: 9633 }
        6001: { pg_cluster: mx-seg1, pg_seq: 2, pg_role: replica , pg_exporter_port: 9634 }
  vars:
    gp_role: segment               # these are nodes for gp segments
    pg_shard: mx                   # pgsql sharding name & gpsql deployment name
    pg_cluster: mx-sdw             # these segment clusters name is mx-sdw
    pg_preflight_skip: true        # skip preflight check (since pg_seq & pg_role & pg_cluster not exists)
    pg_exporter_config: pg_exporter_basic.yml                             # use basic config to avoid segment server crash
    pg_exporter_params: 'options=-c%20gp_role%3Dutility&sslmode=disable'  # use gp_role = utility to connect to segments

16.10 - Supabase

Self-hosting BaaS upon PostgreSQL

See the maintained self-hosting tutorial: Supabase

Supabase is great, but having your own Supabase is even better. Pigsty helps you build enterprise-grade Supabase on your own servers (physical/virtual machines/cloud servers) with one-click deployment — more extensions, better performance, deeper control, and much more cost-effective.

Pigsty is one of the three 3rd party self-hosting tutorials listed in the official Supabase docs


Quick Start

Prepare a Linux server, follow the Pigsty standard installation process, select the supabase configuration template, and execute the following commands:

curl -fsSL https://repo.pigsty.io/get | bash -s v3.7.0; cd ~/pigsty
./configure -c supabase    # Use supabase configuration (please change credentials in pigsty.yml)
vi pigsty.yml              # Edit domain, passwords, keys...
./install.yml              # Install pigsty
./docker.yml               # Install docker compose components
./app.yml                  # Start supabase stateless components with docker (may be slow)

After installation, visit port 8000 in your browser to access Supa Studio, username supabase, password pigsty.


Table of Contents


What is Supabase?

Supabase is a BaaS (Backend as Service), an open-source Firebase alternative, and the most popular database + backend solution in the AI Agent era. Supabase wraps PostgreSQL and provides authentication, messaging, edge functions, object storage, and automatically generates REST API and GraphQL API based on PostgreSQL database schemas.

Supabase aims to provide developers with a one-stop backend solution, reducing the complexity of developing and maintaining backend infrastructure. It allows developers to eliminate most backend development work — developers only need to understand database design and frontend to quickly deliver applications! Developers can quickly complete a full application with just frontend development and database schema design using Vibe Coding.

Currently, Supabase is the most popular open-source project in the PostgreSQL open-source ecosystem, with 80,000 stars on GitHub. Supabase also provides “generous” free cloud service quotas for small entrepreneurs — 500 MB of free space, which is sufficient for storing user tables, view counts, and similar data.


Why Self-Host?

Since Supabase cloud service is so attractive, why self-host?

The most intuitive reason is what we mentioned in “Are Cloud Databases an Intelligence Tax?”: when your data/computing scale exceeds the cloud computing applicable spectrum (Supabase: 4C/8G/500MB free storage), costs can easily explode. Moreover, currently, sufficiently reliable local enterprise-grade NVMe SSDs have a three to four order of magnitude advantage in cost-effectiveness compared to cloud storage, and self-hosting can better leverage this advantage.

Another important reason is functionality — Supabase cloud service functionality is limited. Many powerful PostgreSQL extensions cannot be provided as cloud services due to multi-tenant security challenges and licensing issues. Therefore, although extensions are PostgreSQL’s core feature, only 64 extensions are available on Supabase cloud service. Self-built Supabase with Pigsty provides up to 437 ready-to-use PostgreSQL extensions.

Additionally, autonomy and avoiding vendor lock-in are important reasons for self-hosting — although Supabase aims to provide an open-source alternative to Google Firebase without vendor lock-in, the threshold for self-building enterprise-grade Supabase to high standards is actually quite high. Supabase includes a series of PostgreSQL extension plugins developed and maintained by them, and plans to replace the native PostgreSQL kernel with the acquired OrioleDB, but these kernels and extensions are not provided in the official PGDG repository.

This is actually a form of implicit vendor lock-in, preventing users from self-building using methods other than the supabase/postgres Docker image. Pigsty provides an open-source, transparent, and universal solution to solve this problem. We package all 10 missing extensions developed and used by Supabase into ready-to-use RPM/DEB packages, ensuring they are available on all mainstream Linux operating system distributions:

Extension Description
pg_graphql Provides GraphQL support within PostgreSQL (RUST), Rust extension, provided by PIGSTY
pg_jsonschema Provides JSON Schema validation capability, Rust extension, provided by PIGSTY
wrappers Supabase’s external data source wrapper bundle, Rust extension, provided by PIGSTY
index_advisor Query index advisor, SQL extension, provided by PIGSTY
pg_net Extension for asynchronous non-blocking HTTP/HTTPS requests with SQL (supabase), C extension, provided by PIGSTY
vault Extension for storing encrypted credentials in Vault (supabase), C extension, provided by PIGSTY
pgjwt PostgreSQL implementation of JSON Web Token API (supabase), SQL extension, provided by PIGSTY
pgsodium Table data encryption storage TDE, extension, provided by PIGSTY
supautils Used to ensure database cluster security in cloud environments, C extension, provided by PIGSTY
pg_plan_filter Filter and block specific query statements using execution plan costs, C extension, provided by PIGSTY

Meanwhile, we install most extensions by default in Supabase self-hosting deployment. You can refer to the available extension list to enable them as needed.

Additionally, Pigsty handles the automatic setup of underlying high availability PostgreSQL database clusters, high availability MinIO object storage clusters, and even Docker container infrastructure deployment and Nginx reverse proxy, domain configuration and HTTPS certificate issuance. You can deploy any number of stateless Supabase container clusters using Docker Compose and store state in external Pigsty self-hosted database services.

In this self-hosting deployment architecture, you gain the freedom to use different kernels (PostgreSQL 15-18, OrioleDB), the freedom to install 437 extensions, the freedom to scale Supabase/Postgres/MinIO, the freedom from database operational chores, and the freedom from vendor lock-in to run locally indefinitely. Compared to the cost of using cloud services, the price is just preparing servers and typing a few more commands.


Single Node Quick Start

Let’s start with single-node Supabase deployment. We’ll introduce multi-node high availability deployment methods later.

Prepare a fresh Linux server, use the supabase configuration template provided by Pigsty to execute the standard installation process, then additionally run docker.yml and app.yml to deploy the stateless Supabase containers (default ports 8000/8433).

curl -fsSL https://repo.pigsty.io/get | bash -s v3.7.0; cd ~/pigsty
./configure -c supabase    # Use supabase configuration (please change credentials in pigsty.yml)
vi pigsty.yml              # Edit domain, passwords, keys...
./install.yml              # Install pigsty
./docker.yml               # Install docker compose components
./app.yml                  # Start supabase stateless components with docker

Before deploying Supabase, please modify the parameters (domain and passwords) in the automatically generated pigsty.yml configuration file according to your actual situation. If it’s just local development testing, you can skip this for now. We’ll introduce how to further customize through configuration file modifications later.

asciicast

If configured correctly, after about ten minutes, you can access the Supabase Studio graphical management interface locally via http://<your_ip_address>:8000. The default username and password are: supabase and pigsty.

DockerHub blocked in mainland China

In mainland China, Pigsty uses DockerHub mirror sites provided by 1Panel and 1ms to download Supabase-related images by default, which may be slow. You can also configure proxy and mirror sites yourself, or manually pull images with cd /opt/supabase; docker compose pull. We also provide Supabase self-hosting expert consulting services including complete offline installation solutions.

Using Supabase object storage requires HTTPS/domain

If you need to use object storage functionality, you need to access Supabase via domain and HTTPS, otherwise errors will occur.

Please change passwords for production deployment!

For serious production deployments, must change all default passwords!


Key Technical Decisions for Self-Hosting

Here are some key technical decisions involved in self-hosting Supabase for your reference:

Using the default single-node deployment, Supabase cannot enjoy PostgreSQL/MinIO high availability capabilities. Nevertheless, single-node deployment still has significant advantages compared to the official pure Docker Compose solution: for example, out-of-the-box monitoring systems, the ability to freely install extensions, component scaling capabilities, and providing fallback database point-in-time recovery capabilities.

If you only have one server or choose to self-host on cloud servers, Pigsty recommends using external S3 instead of local MinIO as object storage to store PostgreSQL backups and support Supabase Storage services. Such deployment can provide a fallback-level RTO (hour-level recovery time)/RPO (MB-level data loss) disaster recovery level under single-machine deployment conditions during failures.

In serious production deployments, Pigsty recommends using at least 3-4 node deployment strategies to ensure both MinIO and PostgreSQL use multi-node deployments that meet enterprise-grade high availability requirements. In this case, you need to prepare more nodes and disks accordingly and adjust cluster configurations in the pigsty.yml configuration manifest, as well as access information in supabase cluster configuration to use high availability access points.

Some Supabase functionality requires sending emails, so SMTP services are needed. Unless purely for internal networks, for serious production deployments, using SMTP cloud services is recommended. Self-built email servers easily have their emails marked as spam and rejected.

If your service is directly exposed to the public network, we strongly recommend using real domains and HTTPS certificates and accessing through Nginx Portal.

Next, we’ll discuss some advanced topics in sequence: how to further improve Supabase security, availability, and performance based on single-node deployment.


Advanced Topic: Security Hardening

Pigsty Base Components

For serious production deployments, we strongly recommend changing Pigsty default passwords. Because these default values are public and well-known, going to production without changing passwords is like streaking:

The above passwords are for Pigsty component modules and are strongly recommended to be set before installation and deployment.

Supabase Keys

In addition to Pigsty component passwords, you also need to modify Supabase keys, including:

Please refer to the Supabase tutorial: Securing your services instructions:

  • Generate a JWT_SECRET longer than 40 characters and use the tools in the tutorial to sign ANON_KEY and SERVICE_ROLE_KEY JWTs.
  • Use the tools provided in the tutorial to generate an ANON_KEY JWT based on JWT_SECRET and expiration time attributes. This is the credential for anonymous users.
  • Use the tools provided in the tutorial to generate a SERVICE_ROLE_KEY based on JWT_SECRET and expiration time attributes. This is the credential for higher-privilege service roles.
  • Setup PG_META_CRYPTO_KEY with a random string at least 32 char long for securing connection strings between Studio and postgres-meta
  • If your PostgreSQL business user uses a password different from the default, please modify the POSTGRES_PASSWORD value accordingly
  • If your object storage uses a password different from the default, please modify the S3_ACCESS_KEY and S3_SECRET_KEY values accordingly

After modifying Supabase credentials, you can restart Docker Compose containers to apply the new configuration:

./app.yml -t app_config,app_launch
cd /opt/supabase; make up

Advanced Topic: Domain Integration

If you’re using Supabase on localhost or within a LAN, you can choose IP:Port direct connection to Kong’s exposed HTTP port 8000 to access Supabase.

You can use an internal static DNS domain, but for serious production deployments, we recommend using real domain + HTTPS to access Supabase. In this case, your server should have a public IP address, you should own a domain, use DNS resolution services provided by cloud/DNS/CDN providers to point it to the installation node’s public IP (optional fallback: local /etc/hosts static resolution).

A simple approach is to batch replace the placeholder domain (supa.pigsty) with your actual domain, say supa.pigsty.cc:

sed -ie 's/supa.pigsty/supa.pigsty/g' ~/pigsty/pigsty.yml

If you haven’t configured it beforehand, reload Nginx and Supabase configurations:

make nginx      # Reload nginx configuration
make cert       # Apply for free HTTPS certificate with certbot
./app.yml       # Reload Supabase configuration

The modified configuration should look like the following snippet:

all:
  vars:
    infra_portal:
      supa :
        domain: supa.pigsty.cc        # Replace with your domain!
        endpoint: "10.10.10.10:8000"
        websocket: true
        certbot: supa.pigsty.cc       # Certificate name, usually same as domain

  children:
    supabase:
      vars:
          supabase:                                       # the definition of supabase app
            conf:                                         # override /opt/supabase/.env
              SITE_URL: https://supa.pigsty                # <------- Change This to your external domain name
              API_EXTERNAL_URL: https://supa.pigsty        # <------- Otherwise the storage api may not work!
              SUPABASE_PUBLIC_URL: https://supa.pigsty     # <------- DO NOT FORGET TO PUT IT IN infra_portal!

Complete domain/HTTPS configuration can refer to the Certificate Management tutorial. You can also use Pigsty’s built-in local static resolution and self-signed HTTPS certificates as fallback.

asciicast


Advanced Topic: External Object Storage

You can use S3 or S3-compatible services as object storage for PostgreSQL backups and Supabase usage. Here we use Alibaba Cloud OSS object storage as an example.

Pigsty provides a terraform/spec/aliyun-meta-s3.tf template that can be used to deploy a server and an OSS bucket on Alibaba Cloud.

First, modify the S3-related configuration in all.children.supa.vars.apps.[supabase].conf, pointing it to the Alibaba Cloud OSS bucket:

# if using s3/minio as file storage
S3_BUCKET: data                       # Replace with S3-compatible service connection information
S3_ENDPOINT: https://sss.pigsty:9000  # Replace with S3-compatible service connection information
S3_ACCESS_KEY: s3user_data            # Replace with S3-compatible service connection information
S3_SECRET_KEY: S3User.Data            # Replace with S3-compatible service connection information
S3_FORCE_PATH_STYLE: true             # Replace with S3-compatible service connection information
S3_REGION: stub                       # Replace with S3-compatible service connection information
S3_PROTOCOL: https                    # Replace with S3-compatible service connection information

Reload Supabase configuration with the following command:

./app.yml -t app_config,app_launch

You can also use S3 as PostgreSQL backup repository by adding an aliyun backup repository definition in all.vars.pgbackrest_repo:

all:
  vars:
    pgbackrest_method: aliyun          # pgbackrest backup method: local,minio,[other user-defined repositories...], in this example backup is stored to MinIO
    pgbackrest_repo:                   # pgbackrest backup repository: https://pgbackrest.org/configuration.html#section-repository
      aliyun:                          # Define a new backup repository aliyun
        type: s3                       # Alibaba Cloud OSS is S3-compatible object storage
        s3_endpoint: oss-cn-beijing-internal.aliyuncs.com
        s3_region: oss-cn-beijing
        s3_bucket: pigsty-oss
        s3_key: xxxxxxxxxxxxxx
        s3_key_secret: xxxxxxxx
        s3_uri_style: host
        path: /pgbackrest
        bundle: y                         # bundle small files into a single file
        bundle_limit: 20MiB               # Limit for file bundles, 20MiB for object storage
        bundle_size: 128MiB               # Target size for file bundles, 128MiB for object storage
        cipher_type: aes-256-cbc          # enable AES encryption for remote backup repo
        cipher_pass: pgBackRest.MyPass    # Set an encryption password, pgBackrest backup repository encryption password
        retention_full_type: time         # retention full backup by time on minio repo
        retention_full: 14                # keep full backup for the last 14 days

Then specify using the aliyun backup repository in all.vars.pgbackrest_method and reset pgBackrest backup:

./pgsql.yml -t pgbackrest

Pigsty will switch the backup repository to external object storage. More backup configurations can refer to PostgreSQL Backup documentation.


Advanced Topic: Using SMTP

You can use SMTP to send emails by modifying the supabase application configuration and adding SMTP information:

all:
  children:
    supabase:        # supa group
      vars:          # supa group vars
        apps:        # supa group app list
          supabase:  # the supabase app
            conf:    # the supabase app conf entries
              SMTP_HOST: smtpdm.aliyun.com:80
              SMTP_PORT: 80
              SMTP_USER: [email protected]
              SMTP_PASS: your_email_user_password
              SMTP_SENDER_NAME: MySupabase
              SMTP_ADMIN_EMAIL: [email protected]
              ENABLE_ANONYMOUS_USERS: false

Don’t forget to use app.yml to reload the configuration


Advanced Topic: True High Availability

After these configurations, you have an enterprise-grade Supabase (basic single-machine version) with public domain, HTTPS certificate, SMTP, PITR backup, monitoring, IaC, and 400+ extensions. For high availability configuration, please refer to other parts of Pigsty documentation. If you’re too lazy to read and learn, we provide hands-on Supabase self-hosting expert consulting services — ¥2000 to save you from the hassle of tinkering and downloading.

Single-node RTO/RPO relies on external object storage services for fallback. If your node fails, backups are retained in external S3 storage, and you can redeploy Supabase on a new node and restore from backup. Such deployment can provide a minimum standard RTO (hour-level recovery time)/RPO (MB-level data loss) fallback disaster recovery level during failures.

To achieve RTO < 30s with zero data loss failover, you need to use multi-node high availability deployment, which involves:

  • ETCD: DCS needs three or more nodes to tolerate one node failure.
  • PGSQL: PostgreSQL synchronous commit mode without data loss, recommend using at least three nodes.
  • INFRA: Monitoring infrastructure failure has less impact, recommend using dual replicas in production
  • Supabase stateless containers themselves can also be multi-node replicas to achieve high availability.

In this case, you also need to modify PostgreSQL and MinIO access points to use DNS/L2 VIP/HAProxy and other high availability access points For these parts, you only need to refer to the documentation of each module in Pigsty for configuration and deployment. We recommend referring to the configurations in conf/ha/trio.yml and conf/ha/safe.yml to upgrade cluster scale to three nodes or more.

16.11 - FerretDB

Mongo Wire Compatible PostgreSQL

FerretDB is an open-source MongoDB wire protocol compatible middleware that allows you to use PostgreSQL as a drop-in replacement for MongoDB. It enables applications that rely on MongoDB’s wire protocol to work seamlessly with PostgreSQL, providing a bridge between the two databases.

To enable FerretDB, you’ll need the FerretDB patched documentdb extension, which is also available in the Pigsty repository. The latest combo is FerretDB 2.7 and DocumentDB 0.107.0.


Get Started

Use Pigsty’s standard installation process with the mongo configuration template.

./configure -c mongo    # Use FerretDB / DocumentDB config template
./install.yml           # Install, for production deployment please modify passwords in pigsty.yml first

For production deployment, please ensure to modify the password parameters in the pigsty.yml configuration file before running the installation playbook.


Configuration

pg-meta:
  hosts:
    10.10.10.10: { pg_seq: 1, pg_role: primary }
  vars:
    pg_cluster: pg-meta
    pg_users:
      - { name: mongod      ,password: DBUser.Mongo  ,pgbouncer: true ,roles: [dbrole_admin   ] ,comment: ferretdb super user ,superuser: true }
      - { name: dbuser_meta ,password: DBUser.Meta   ,pgbouncer: true ,roles: [dbrole_admin   ] ,comment: pigsty admin user }
      - { name: dbuser_view ,password: DBUser.Viewer ,pgbouncer: true ,roles: [dbrole_readonly] ,comment: read-only viewer  }
    pg_databases:
      - {name: meta, owner: mongod ,baseline: cmdb.sql ,comment: pigsty meta database ,schemas: [pigsty] ,extensions: [ documentdb, postgis, vector, pg_cron, rum ]}
    pg_hba_rules:
      - { user: dbuser_view , db: all ,addr: infra ,auth: pwd ,title: 'allow grafana dashboard access cmdb from infra nodes' }
      - { user: mongod      , db: all ,addr: world ,auth: pwd ,title: 'mongodb password access from everywhere' }
    node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ] # make a full backup every 1am

    # DocumentDB Settings
    pg_extensions: [ documentdb, citus, postgis, pgvector, pg_cron, rum ]
    pg_libs: 'pg_documentdb, pg_documentdb_core, pg_cron, pg_stat_statements, auto_explain'  # add timescaledb to shared_preload_libraries
    pg_parameters: { cron.database_name: meta }

Usage

Check the FERRET docs for the details.

Install Client Tools

You can use MongoDB’s command-line tool MongoSH to access FerretDB.

Use the pig command to add MongoDB repository, then install mongosh using yum or apt:

pig repo add mongo -u
yum install mongodb-mongosh
apt install mongodb-mongosh

Connect to FerretDB

You can access FerretDB using MongoDB connection strings with any MongoDB driver in any language. Here’s an example using the mongosh CLI tool:

$ mongosh
Current Mongosh Log ID:	67ba8c1fe551f042bf51e943
Connecting to:		mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.4.0
Using MongoDB:		7.0.77
Using Mongosh:		2.4.0

For mongosh info see: https://www.mongodb.com/docs/mongodb-shell/

test>

Authentication

You can log in with different users. See FerretDB: Authentication for details.

mongosh 'mongodb://dbuser_meta:[email protected]:27017/meta'      # Business admin user
mongosh 'mongodb://dbuser_view:[email protected]:27017/meta'    # Read-only user

Quick Start

You can connect to FerretDB and use it as if it were a MongoDB cluster.

$ mongosh 'mongodb://dbuser_meta:[email protected]:27017/meta'

MongoDB commands are translated to SQL and executed in the underlying PostgreSQL:

use test                            // CREATE SCHEMA test;
db.dropDatabase();                  // DROP SCHEMA test;
db.createCollection('posts');       // CREATE TABLE posts(_data JSONB,...)
db.posts.insertOne({                // INSERT INTO posts VALUES(...);
    title: 'Post One',body: 'Body of post one',category: 'News',tags: ['news', 'events'],
    user: {name: 'John Doe',status: 'author'},date: Date()}
);
db.posts.find().limit(2).pretty();  // SELECT * FROM posts LIMIT 2;
db.posts.createIndex({ title: 1 })  // CREATE INDEX ON posts(_data->>'title');

If you’re not familiar with MongoDB, here’s a quick tutorial that works with FerretDB: Perform CRUD Operations with MongoDB Shell

To generate sample workload, you can use this simple test script with mongosh:

cat > benchmark.js <<'EOF'
const coll = "testColl";
const numDocs = 1000;

for (let i = 0; i < numDocs; i++) {  // insert
  db.getCollection(coll).insertOne({ num: i, name: "MongoDB Benchmark Test" });
}

for (let i = 0; i < numDocs; i++) {  // select
  db.getCollection(coll).find({ num: i });
}

for (let i = 0; i < numDocs; i++) {  // update
  db.getCollection(coll).updateOne({ num: i }, { $set: { name: "Updated" } });
}

for (let i = 0; i < numDocs; i++) {  // delete
  db.getCollection(coll).deleteOne({ num: i });
}
EOF

mongosh 'mongodb://dbuser_meta:[email protected]:27017' benchmark.js

You can check FerretDB’s supported MongoDB commands and known differences. For basic usage, these differences are usually not significant.

17 - Extension

Harness the synergistic power of PostgreSQL extensions

Pigsty allows you to harness the synergistic superpower of the Postgres extensions ecosystem with 3 things: Catalog, Repo, and pig.

Extension Catalog
    The complete list of <span class="text-lg font-black text-emerald-500">437</span> available PostgreSQL extensions
Software Repository
    The APT/YUM repo that deliver PostgreSQL extensions
Package Manger
    The missing package manager for PostgreSQL & Extensions
Quick Start
    How to get, install, config, manage these extensions?

There are 437 PostgreSQL extensions in the v3.7.0 catalog. PostgreSQL 18 is the default in v3.7.0.

The per-major figures below are the archive’s PG13–17 compatibility snapshot; the final PG18 breakdown was not recorded in this table. Use the v3.7.0 package aliases and release note for PG18.

Distro All PGDG PIGSTY CONTRIB OTHER MISS PG17 PG16 PG15 PG14 PG13
EL 417 119 227 71 0 6 399 407 410 394 368
Debian 410 103 236 71 0 13 397 400 403 391 363

ecosystem

TIME GIS RAG FTS OLAP FEAT LANG TYPE UTIL FUNC ADMIN STAT SEC FDW SIM ETL

MIT ISC PostgreSQL BSD-0 BSD-2 BSD-3 Artistic Apache-2.0 MPL-2.0 GPL-2.0 GPL-3.0 LGPL-2.1 LGPL-3.0 AGPL-3.0 Timescale


Usage

Package
    Download and install extensions with package alias
Download
    Download Extensions from PGDG / Pigsty Repo
Install
    Install Postgres Extension Packages
Config
    Configure extensions and setup pre-loading
Download
    Download Extensions from PGDG / Pigsty Repo
Install
    Install Postgres Extension Packages
Config
    Configure extensions and setup pre-loading
Create
    CREATE Postgres Extension in Database
Update
    Upgrade Postgres Extension
Remove
    Uninstall Postgres Extension

Index

Category Extensions
TIME emaj periods pg_background pg_cron pg_later pg_task table_version temporal_tables timescaledb timescaledb_toolkit timeseries
GIS address_standardizer address_standardizer_data_us earthdistance geoip h3 h3_postgis mobilitydb ogr_fdw pg_geohash pg_polyline pgrouting pointcloud pointcloud_postgis postgis postgis_raster postgis_sfcgal postgis_tiger_geocoder postgis_topology q3c tzf
RAG pg4ml pg_similarity pg_summarize pg_tiktoken pgml smlar vchord vector vectorize vectorscale
FTS fuzzystrmatch hunspell_cs_cz hunspell_de_de hunspell_en_us hunspell_fr hunspell_ne_np hunspell_nl_nl hunspell_nn_no hunspell_pt_pt hunspell_ru_ru hunspell_ru_ru_aot pg_bestmatch pg_bigm pg_search pg_tokenizer pg_trgm pgroonga pgroonga_database vchord_bm25 zhparser
OLAP citus citus_columnar columnar duckdb_fdw pg_analytics pg_duckdb pg_fkpart pg_mooncake pg_parquet pg_partman pg_strom plproxy tablefunc
FEAT age bloom hll hypopg imgsmlr index_advisor jsquery omni omni_auth omni_aws omni_cloudevents omni_containers omni_credentials omni_email omni_http omni_httpc omni_httpd omni_id omni_json omni_kube omni_ledger omni_manifest omni_mimetypes omni_os omni_polyfill omni_python omni_regex omni_rest omni_schema omni_seq omni_service omni_session omni_sql omni_sqlite omni_test omni_txn omni_types omni_var omni_vfs omni_vfs_types_v1 omni_web omni_worker omni_xml omni_yaml orioledb pg_cardano pg_graphql pg_hint_plan pg_incremental pg_ivm pg_jsonschema pgmq pgq plan_filter rdkit rum
LANG bool_plperl bool_plperlu dbt2 faker hstore_pllua hstore_plluau hstore_plperl hstore_plperlu hstore_plpython3u jsonb_plperl jsonb_plperlu jsonb_plpython3u ltree_plpython3u pg_tle pgtap pldbgapi pljava pllua plluau plperl plperlu plpgsql plpgsql_check plprofiler plprql plpython3u plr plsh pltcl pltclu plv8
TYPE acl asn1oid chkpass citext collection country cube currency debversion emailaddr hashtypes hstore ip4r isn l10n_table_dependent_extension ltree md5hash numeral pg_duration pg_rational pg_rrule pg_sphere pg_xenophile pgfaceting pglite_fusion pgmp pgpdf prefix roaringbitmap seg semver timestamp9 uint uint128 unit uri xml2
UTIL bzip cryptint data_historization ddl_historization envvar floatfile gzip hashlib http icu_ext pg_curl pg_extra_time pg_html5_email_address pg_net pg_protobuf pg_readme pg_readme_test_extension pg_render pg_smtp_client pgjq pgjwt pgpcre pgqr pgsql_tweaks pguecc schedoc shacrypt sparql url_encode xxhash zstd
FUNC aggs_for_arrays aggs_for_vecs arraymath autoinc base36 base62 btree_gin btree_gist convert count_distinct ddsketch dict_int dict_xsyn extra_window_functions financial first_last_agg floatvec insert_username intagg intarray lower_quantile moddatetime omnisketch permuteseq pg_base58 pg_hashids pg_idkit pg_math pg_uuidv7 pgx_ulid quantile random refint sequential_uuids tcn tdigest topn tsm_system_rows tsm_system_time unaccent uuid-ossp vasco xicor
ADMIN adminpack amcheck basebackup_to_shell basic_archive ddlx fio lo old_snapshot pg_catcheck pg_cheat_funcs pg_checksums pg_cooldown pg_crash pg_dirtyread pg_drop_events pg_orphaned pg_permissions pg_prewarm pg_readonly pg_repack pg_savior pg_squeeze pg_surgery pg_upless pgagent pgautofailover pgcozy pgdd pgfincore pgpool_adm pgpool_recovery pgpool_regclass pre_prepare prioritize safeupdate table_log
STAT auto_explain bgw_replstatus explain_ui meta pageinspect pagevis pg_buffercache pg_freespacemap pg_logicalinspect pg_overexplain pg_proctab pg_profile pg_qualstats pg_relusage pg_show_plans pg_sqlog pg_stat_kcache pg_stat_monitor pg_stat_statements pg_store_plans pg_tracing pg_track_settings pg_visibility pg_wait_sampling pg_walinspect pgmeminfo pgnodemx pgrowlocks pgsentinel pgstattuple powa sslinfo system_stats toastinfo
SEC anon auth_delay credcheck logerrors login_hook noset passwordcheck passwordcheck_cracklib pg_auditor pg_auth_mon pg_jobmon pg_session_jwt pg_snakeoil pg_tde pgaudit pgauditlogtofile pgcrypto pgcryptokey pgextwlist pgsmcrypto pgsodium sepgsql set_user sslutils supabase_vault supautils
FDW aws_s3 db2_fdw dblink file_fdw firebird_fdw hdfs_fdw jdbc_fdw kafka_fdw log_fdw mongo_fdw multicorn mysql_fdw odbc_fdw oracle_fdw pgbouncer_fdw pgspider_ext postgres_fdw redis redis_fdw sqlite_fdw tds_fdw wrappers
SIM babelfishpg_common babelfishpg_money babelfishpg_tds babelfishpg_tsql documentdb documentdb_core documentdb_distributed orafce pg_dbms_job pg_dbms_lock pg_dbms_metadata pg_statement_rollback pgmemcache pgtt session_variable spat
ETL db_migrator decoder_raw decoderbufs mimeo pg_bulkload pg_fact_loader pg_failover_slots pgactive pgl_ddl_deploy pglogical pglogical_origin pglogical_ticker pgoutput repmgr test_decoding wal2json wal2mongo

17.1 - Quick Start

Install, Load, create, update PostgreSQL extensions

There are unparalleled 437 extensions available in Pigsty for 14 mainstream Linux distros.


Overview

It takes 4 steps to deliver an extension: downloads, installs, config, and create:

Step 1

    [**Download**](#download-extension) : Which extension packages to download

    ```yaml tab="config" title="define which extensions to be downloaded"
    repo_extra_packages: [ postgis, timescaledb, vector ]
    ```
    ```bash tab="apply" title="download package"
    make repo
    ```

Step 2

    [Install](#install-extension) : Which extensions to be installed

    ```yaml tab="config"
    pg_extensions: [ postgis, pgvector, timescaledb ]
    ```
    ```bash tab="apply"
    ./pgsql.yml -t pg_ext     # install extensions
    ```

Step 3

    [**Load**](#load-extension) : Which extensions to be pre-loaded

    ```yaml tab="config"
    pg_libs: 'timescaledb, pg_stat_statements, auto_explain'  # add extension to preload libraries (not all extensions need this)
    ```
    ```bash tab="apply" title="edit existing cluster config and reload"
    pg edit-config --force -p shared_preload_libraries='timescaledb, pg_stat_statements, auto_explain'
    ```

Step 4

    [**Create**](#create-extension) : Create extension in the [database](/docs/pgsql/db)

    ```yaml tab="config"
    pg_databases:
    - { name: meta ,extensions: [ postgis, timescaledb, vector ] }
    ```
    ```sql tab="apply" title="create extension in existing database"
    CREATE EXTENSION postgis CASCADE;
    ```

Quick Start

You can describe extensions in the config inventory, and pigsty will download, install, configure, and enable extensions for you. This example makes postgis, pgvector, timescaledb out-of-the-box:

all:
  children:
    pg-meta:
      hosts: {10.10.10.10: { pg_seq: 1, pg_role: primary }}
      vars:
        pg_cluster: pg-meta
        pg_databases: {name: meta, extensions: [ postgis, vector ]} # create (in database)
        pg_extensions: [ postgis, pgvector ]                        # install (in cluster)
  vars:
    repo_extra_packages: [ postgis, timescaledb, vector ]           # download  (globally)

When you init this PG cluster, these extensions will be made available for you in the pg-meta cluster.

Here’s a more complicated example: launch Postgres with required extensions for self-hosting supabase:

all:
  children:
    pg-meta:
      hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
      vars:
        pg_cluster: pg-meta
        pg_databases:
          - name: postgres
            baseline: supabase.sql
            schemas: [ extensions ,auth ,realtime ,storage ,graphql_public ,supabase_functions ,_analytics ,_realtime ]
            extensions:                                 # Extensions to enable in the postgres database
              - { name: pgcrypto  ,schema: extensions } # Encryption functions
              - { name: pg_net    ,schema: extensions } # Asynchronous HTTP
              - { name: pgjwt     ,schema: extensions } # JSON Web Token API for PostgreSQL
              - { name: uuid-ossp ,schema: extensions } # Generate universally unique identifiers (UUIDs)
              - { name: pgsodium        }               # Modern cryptography for PostgreSQL
              - { name: supabase_vault  }               # Supabase Vault extension
              - { name: pg_graphql      }               # GraphQL support
              - { name: pg_jsonschema   }               # JSON schema validation
              - { name: wrappers        }               # Collection of foreign data wrappers
              - { name: http            }               # Web page retrieval within the database
              - { name: pg_cron         }               # Job scheduler for PostgreSQL
              - { name: timescaledb     }               # Time-series data support
              - { name: pg_tle          }               # Trusted Language Extensions for PostgreSQL
              - { name: vector          }               # Vector similarity search
              - { name: pgmq            }               # Lightweight message queue
        # supabase required extensions for loading
        pg_libs: 'timescaledb, plpgsql, plpgsql_check, pg_cron, pg_net, pg_stat_statements, auto_explain, pg_tle, plan_filter'
        pg_parameters:
          cron.database_name: postgres
          pgsodium.enable_event_trigger: off
  vars:
    pg_version: 17
    repo_extra_packages: [pg17-core ,pg17-time ,pg17-gis ,pg17-rag ,pg17-fts ,pg17-olap ,pg17-feat ,pg17-lang ,pg17-type ,pg17-util ,pg17-func ,pg17-admin ,pg17-stat ,pg17-sec ,pg17-fdw ,pg17-sim ,pg17-etl ]
    pg_extensions:                  [pg17-time ,pg17-gis ,pg17-rag ,pg17-fts ,pg17-feat ,pg17-lang ,pg17-type ,pg17-util ,pg17-func ,pg17-admin ,pg17-stat ,pg17-sec ,pg17-fdw ,pg17-sim ,pg17-etl ] #,pg17-olap]

All available extensions for PG 17 are downloaded and installed, and required ones are loaded & enabled.

17.2 - Package

Extension Packages and Alias

Mange extensions and packages are not that simple, here are two common extension examples:

Entity Example pgvector Example postgis
Extension vector postgis, postgis_topology, postgis_raster,…
Package pgvector postgis
OS PKG pgvector_18 postgresql-16-postgis-3
RPM/DEB pgvector_18_0.8.1-1PGDG.rhel8.x86_64.rpm postgresql-17-postgis-3_3.5.2+dfsg-1.pgdg22.04+1_amd64.deb

To install the right RPM / DEB with minimal effort, we need to use the abstract layer: package alias. So you can install these extensions by specifying the “Normalized” names, like pgvector or postgis. Without knowing any details about PG & OS version, Arch, Extension versions, and any other details.

Package alias pkg are used for extension download & install, but you’ll have to use the extension name ext when CREATE EXTENSION in the database (like the vector in meta database). And beware some extensions require explicit preloading, like the timescaledb in the above example.

Besides, all the extensions are categorized into 16 major categories, we also have alias for the entire extension category so that you can download and install them in batch, such as:

replace 17 with 16,15,14,13,...
repo_extra_packages: [ pg17-main ,pg17-core ,pg17-time ,pg17-gis ,pg17-rag ,pg17-fts ,pg17-olap ,pg17-feat ,pg17-lang ,pg17-type ,pg17-util ,pg17-func ,pg17-admin ,pg17-stat ,pg17-sec ,pg17-fdw ,pg17-sim ,pg17-etl]
pg_extensions: [pg17-time ,pg17-gis ,pg17-rag ,pg17-fts ,pg17-feat ,pg17-lang ,pg17-type ,pg17-util ,pg17-func ,pg17-admin ,pg17-stat ,pg17-sec ,pg17-fdw ,pg17-sim ,pg17-etl ] #,pg17-olap]

All extensions CAN be installed simultaneously, except the olap category, where citus conflict with hydra, and pg_duckdb conflict with pg_mooncake. So you can download them all, but install one at a time.

17.3 - Download

Download PostgreSQL Extension

In Pigsty, downloading and installing extensions are separate steps. During INFRA module installation, Pigsty downloads all required software to the local machine and creates a local YUM/APT repo for the entire deployment.

This approach accelerates installation, eliminates redundant downloads, removes the need for database nodes to access the internet, reduces network traffic, improves delivery reliability, and ensures consistent versions across your environment - all best practices for production deployments.

For development environments, installing extensions directly from internet repo is also acceptable


Quick Start

Packages defined in repo_packages and repo_extra_packages are automatically downloaded to your local repo during Pigsty installation.

For PostgreSQL-related packages (core and extensions), typically put them in repo_extra_packages while leaving repo_packages with its os-specific global defaults.

The default value for repo_extra_packages is [pgsql-main], an alias representing core PostgreSQL and critical extensions for the current active major version.

repo_extra_packages: [ pgsql-main ]  # main packages (kernel + 3 extension) for current pg major 18

To add specific extensions, simply add Pigsty extension package name (pkg) to this parameter. Pigsty automatically downloads the appropriate packages for your active PG version and current OS distro.

repo_extra_packages: [ pgsql-main, documentdb, citus, postgis, pgvector, pg_cron, rum ]

To download all available extensions for the current PG version, add all 16 extension category aliases (as in the rich config template):

repo_extra_packages: [ pgsql-main ,pgsql-time ,pgsql-gis ,pgsql-rag ,pgsql-fts ,pgsql-olap ,pgsql-feat ,pgsql-lang ,pgsql-type ,pgsql-util ,pgsql-func ,pgsql-admin ,pgsql-stat ,pgsql-sec ,pgsql-fdw ,pgsql-sim ,pgsql-etl]

Alternatively, use version-specific aliases to download extensions for multiple PostgreSQL versions:

repo_extra_packages: [
    pg18-core,pg18-time,pg18-gis,pg18-rag,pg18-fts,pg18-olap,pg18-feat,pg18-lang,pg18-type,pg18-util,pg18-func,pg18-admin,pg18-stat,pg18-sec,pg18-fdw,pg18-sim,pg18-etl,
    pg17-core,pg17-time,pg17-gis,pg17-rag,pg17-fts,pg17-olap,pg17-feat,pg17-lang,pg17-type,pg17-util,pg17-func,pg17-admin,pg17-stat,pg17-sec,pg17-fdw,pg17-sim,pg17-etl,
    pg16-core,pg16-time,pg16-gis,pg16-rag,pg16-fts,pg16-olap,pg16-feat,pg16-lang,pg16-type,pg16-util,pg16-func,pg16-admin,pg16-stat,pg16-sec,pg16-fdw,pg16-sim,pg16-etl,
    pg15-core,pg15-time,pg15-gis,pg15-rag,pg15-fts,pg15-olap,pg15-feat,pg15-lang,pg15-type,pg15-util,pg15-func,pg15-admin,pg15-stat,pg15-sec,pg15-fdw,pg15-sim,pg15-etl,
    pg14-core,pg14-time,pg14-gis,pg14-rag,pg14-fts,pg14-olap,pg14-feat,pg14-lang,pg14-type,pg14-util,pg14-func,pg14-admin,pg14-stat,pg14-sec,pg14-fdw,pg14-sim,pg14-etl,
    pg13-core,pg13-time,pg13-gis,pg13-rag,pg13-fts,pg13-olap,pg13-feat,pg13-lang,pg13-type,pg13-util,pg13-func,pg13-admin,pg13-stat,pg13-sec,pg13-fdw,pg13-sim,pg13-etl,
]

To add new extensions to your local repo, modify the parameters above and run:

./infra.yml -t repo_build   # Re-download and rebuild local repo

To refresh the repo metadata on all other nodes in your environment, run:

./node.yml  -t node_repo    # [Optional] apt update / yum makecache

Alias Mapping

PostgreSQL has a rich open-source ecosystem with numerous packages across different systems and architectures.

Pigsty provides an abstraction layer that categorizes PostgreSQL packages into “aliases,” hiding differences between systems, architectures, and PG versions.

In the Quick Start section, we used aliases like pgsql-main and pgsql-core. These aliases are translated into specific package names based on your system and architecture. For EL systems, pgsql-main expands to postgresql$v* kernel packages with pgvector_$v*, pg_repack_$v*, and wal2json_$v* extension packages.

pgsql-main:   "postgresql$v* pg_repack_$v* wal2json_$v* pgvector_$v*"

The $v placeholder is replaced by the pg_version value (default: 18) to target the correct version. The * wildcard expands to include all package variants (e.g., server, libs, contrib, devel). Pigsty handles these details automatically.

The complete list of available packages and aliases is in roles/node_id/vars/<os_package>.yml. Here are commonly used aliases available across all supported systems:

postgresql:   "postgresql$v*"
pgsql-main:   "postgresql$v* pg_repack_$v* wal2json_$v* pgvector_$v*"
pgsql-core:   "postgresql$v postgresql$v-server postgresql$v-libs postgresql$v-contrib postgresql$v-plperl postgresql$v-plpython3 postgresql$v-pltcl postgresql$v-test postgresql$v-devel postgresql$v-llvmjit"
pgsql-simple: "postgresql$v postgresql$v-server postgresql$v-libs postgresql$v-contrib postgresql$v-plperl postgresql$v-plpython3 postgresql$v-pltcl"
pgsql-client: "postgresql$v"
pgsql-server: "postgresql$v-server postgresql$v-libs postgresql$v-contrib"
pgsql-devel:  "postgresql$v-devel"
pgsql-basic:  "pg_repack_$v* wal2json_$v* pgvector_$v*"

pgsql-time:   "timescaledb-tsl_$v* timescaledb-toolkit_$v pg_timeseries_$v periods_$v* temporal_tables_$v* e-maj_$v table_version_$v pg_cron_$v* pg_task_$v* pg_later_$v pg_background_$v*"
pgsql-gis:    "postgis35_$v* pgrouting_$v* pointcloud_$v* h3-pg_$v* q3c_$v* ogr_fdw_$v* geoip_$v pg_polyline_$v pg_geohash_$v*"
pgsql-rag:    "pgvector_$v* vchord_$v pgvectorscale_$v pg_vectorize_$v pg_similarity_$v* smlar_$v* pg_summarize_$v pg_tiktoken_$v pg4ml_$v"
pgsql-fts:    "pg_search_$v pgroonga_$v* pg_bigm_$v* zhparser_$v* pg_bestmatch_$v vchord_bm25_$v hunspell_cs_cz_$v hunspell_de_de_$v hunspell_en_us_$v hunspell_fr_$v hunspell_ne_np_$v hunspell_nl_nl_$v hunspell_nn_no_$v hunspell_ru_ru_$v hunspell_ru_ru_aot_$v"
pgsql-olap:   "citus_$v* pg_analytics_$v pg_duckdb_$v* pg_mooncake_$v* duckdb_fdw_$v* pg_parquet_$v pg_fkpart_$v pg_partman_$v* plproxy_$v*" #hydra_$v* #pg_strom_$v*
pgsql-feat:   "hll_$v* rum_$v pg_graphql_$v pg_jsonschema_$v jsquery_$v* pg_hint_plan_$v* hypopg_$v* index_advisor_$v pg_plan_filter_$v* imgsmlr_$v* pg_ivm_$v* pg_incremental_$v* pgmq_$v pgq_$v* pg_cardano_$v omnigres_$v" #apache-age_$v*
pgsql-lang:   "pg_tle_$v* plv8_$v* pllua_$v* pldebugger_$v* plpgsql_check_$v* plprofiler_$v* plsh_$v* pljava_$v*" #plprql_$v #plr_$v* #pgtap_$v* #postgresql_faker_$v* #dbt2-pgsql-extensions*
pgsql-type:   "prefix_$v* semver_$v* postgresql-unit_$v* pgpdf_$v* pglite_fusion_$v md5hash_$v* asn1oid_$v* pg_roaringbitmap_$v* pgfaceting_$v pgsphere_$v* pg_country_$v* pg_xenophile_$v pg_currency_$v* pgcollection_$v* pgmp_$v* numeral_$v* pg_rational_$v* pguint_$v* pg_uint128_$v* hashtypes_$v* ip4r_$v* pg_duration_$v* pg_uri_$v* pg_emailaddr_$v* acl_$v* timestamp9_$v* chkpass_$v*"
pgsql-util:   "pgsql_gzip_$v* pg_bzip_$v* pg_zstd_$v* pgsql_http_$v* pg_net_$v* pg_curl_$v* pgjq_$v* pgjwt_$v pg_smtp_client_$v pg_html5_email_address_$v url_encode_$v* pgsql_tweaks_$v pg_extra_time_$v pgpcre_$v icu_ext_$v* pgqr_$v* pg_protobuf_$v pg_envvar_$v* floatfile_$v* pg_readme_$v ddl_historization_$v data_historization_$v pg_schedoc_$v pg_hashlib_$v pg_xxhash_$v* postgres_shacrypt_$v* cryptint_$v* pg_ecdsa_$v* pgsparql_$v"
pgsql-func:   "pg_idkit_$v pg_uuidv7_$v* permuteseq_$v* pg_hashids_$v* sequential_uuids_$v topn_$v* quantile_$v* lower_quantile_$v* count_distinct_$v* omnisketch_$v* ddsketch_$v* vasco_$v* pgxicor_$v* tdigest_$v* first_last_agg_$v extra_window_functions_$v* floatvec_$v* aggs_for_vecs_$v* aggs_for_arrays_$v* pg_arraymath_$v* pg_math_$v* pg_random_$v* pg_base36_$v* pg_base62_$v* pg_base58_$v pg_financial_$v*"
pgsql-admin:  "pg_repack_$v* pg_squeeze_$v* pg_dirtyread_$v* pgfincore_$v* pg_cooldown_$v* ddlx_$v pg_prioritize_$v* pg_readonly_$v* pg_upless_$v pg_permissions_$v pg_catcheck_$v* preprepare_$v* pgcozy_$v pg_orphaned_$v* pg_crash_$v* pg_cheat_funcs_$v* pg_fio_$v pg_savior_$v* safeupdate_$v* pg_drop_events_$v table_log_$v" #pg_checksums_$v* #pg_auto_failover_$v* #pgagent_$v* #pgpool-II-pgsql-extensions
pgsql-stat:   "pg_profile_$v* pg_tracing_$v* pg_show_plans_$v* pg_stat_kcache_$v* pg_stat_monitor_$v* pg_qualstats_$v* pg_store_plans_$v* pg_track_settings_$v pg_wait_sampling_$v* system_stats_$v* pg_meta_$v pgnodemx_$v pg_sqlog_$v bgw_replstatus_$v* pgmeminfo_$v* toastinfo_$v* pg_explain_ui_$v pg_relusage_$v pagevis_$v powa_$v*"
pgsql-sec:    "passwordcheck_cracklib_$v* supautils_$v* pgsodium_$v* vault_$v* pg_session_jwt_$v pg_anon_$v pgsmcrypto_$v pgaudit_$v* pgauditlogtofile_$v* pg_auth_mon_$v* credcheck_$v* pgcryptokey_$v pg_jobmon_$v logerrors_$v* login_hook_$v* set_user_$v* pg_snakeoil_$v* pgextwlist_$v* pg_auditor_$v sslutils_$v* noset_$v*" #pg_tde_$v*
pgsql-fdw:    "wrappers_$v multicorn2_$v* odbc_fdw_$v* mysql_fdw_$v* tds_fdw_$v* sqlite_fdw_$v* pgbouncer_fdw_$v redis_fdw_$v* pg_redis_pubsub_$v* hdfs_fdw_$v* firebird_fdw_$v aws_s3_$v log_fdw_$v*" #jdbc_fdw_$v* #oracle_fdw_$v* #db2_fdw_$v* #mongo_fdw_$v* #kafka_fdw_$v
pgsql-sim:    "documentdb_$v* orafce_$v pgtt_$v* session_variable_$v* pg_statement_rollback_$v* pg_dbms_metadata_$v pg_dbms_lock_$v pgmemcache_$v*" #pg_dbms_job_$v #wiltondb
pgsql-etl:    "pglogical_$v* pglogical_ticker_$v* pgl_ddl_deploy_$v* pg_failover_slots_$v* db_migrator_$v wal2json_$v* postgres-decoderbufs_$v* decoder_raw_$v* mimeo_$v pg_fact_loader_$v* pg_bulkload_$v*" #wal2mongo_$v* #repmgr_$v*

When using these aliases, the $v placeholder is replaced with the PostgreSQL major version number from pg_version (default: 18).

To download packages for different PostgreSQL versions, either:

  • Change the pg_version parameter, or
  • Use version-specific aliases by replacing the pgsql- prefix with pg18-, pg17-, pg16-, etc.

Not all extensions are available on all systems. Some extensions are commented out in the aliases because they:

  • Are unavailable on specific systems
  • Have extensive dependencies (like pl/R)
  • Depend on commercial software (like oracle_fdw)
  • Are unavailable in the latest PG 18 but available in earlier versions

You can still manually add these extensions if needed.

17.4 - Install

Install PostgreSQL Extension

Pigsty piggyback on standard OS package managers (yum/apt) to install PostgreSQL extensions.


Quick Start

When installing extensions, Pigsty uses the same alias mapping in the download section.

Install all extensions explicitly specified in the pg_extensions parameter, for the cluster pg-meta:

all:
  children:
    pg-meta:
      hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
      vars:
        pg_cluster: pg-meta
        pg_extensions: # extensions to be installed on this cluster
          - timescaledb timescaledb_toolkit pg_timeseries periods temporal_tables emaj table_version pg_cron pg_task pg_later pg_background
          - postgis pgrouting pointcloud pg_h3 q3c ogr_fdw geoip pg_polyline pg_geohash #mobilitydb
          - pgvector vchord pgvectorscale pg_vectorize pg_similarity smlar pg_summarize pg_tiktoken pg4ml #pgml
          - pg_search pgroonga pg_bigm zhparser pg_bestmatch vchord_bm25 hunspell
          - citus hydra pg_analytics pg_duckdb pg_mooncake duckdb_fdw pg_parquet pg_fkpart pg_partman plproxy #pg_strom
          - age hll rum pg_graphql pg_jsonschema jsquery pg_hint_plan hypopg index_advisor pg_plan_filter imgsmlr pg_ivm pg_incremental pgmq pgq pg_cardano omnigres #rdkit
          - pg_tle plv8 pllua plprql pldebugger plpgsql_check plprofiler plsh pljava #plr #pgtap #faker #dbt2
          - pg_prefix pg_semver pgunit pgpdf pglite_fusion md5hash asn1oid roaringbitmap pgfaceting pgsphere pg_country pg_xenophile pg_currency pg_collection pgmp numeral pg_rational pguint pg_uint128 hashtypes ip4r pg_uri pgemailaddr pg_acl timestamp9 chkpass #pg_duration #debversion #pg_rrule
          - pg_gzip pg_bzip pg_zstd pg_http pg_net pg_curl pgjq pgjwt pg_smtp_client pg_html5_email_address url_encode pgsql_tweaks pg_extra_time pgpcre icu_ext pgqr pg_protobuf envvar floatfile pg_readme ddl_historization data_historization pg_schedoc pg_hashlib pg_xxhash shacrypt cryptint pg_ecdsa pgsparql
          - pg_idkit pg_uuidv7 permuteseq pg_hashids sequential_uuids topn quantile lower_quantile count_distinct omnisketch ddsketch vasco pgxicor tdigest first_last_agg extra_window_functions floatvec aggs_for_vecs aggs_for_arrays pg_arraymath pg_math pg_random pg_base36 pg_base62 pg_base58 pg_financial
          - pg_repack pg_squeeze pg_dirtyread pgfincore pg_cooldown pg_ddlx pg_prioritize pg_checksums pg_readonly pg_upless pg_permissions pgautofailover pg_catcheck preprepare pgcozy pg_orphaned pg_crash pg_cheat_funcs pg_fio pg_savior safeupdate pg_drop_events table_log #pgagent #pgpool
          - pg_profile pg_tracing pg_show_plans pg_stat_kcache pg_stat_monitor pg_qualstats pg_store_plans pg_track_settings pg_wait_sampling system_stats pg_meta pgnodemx pg_sqlog bgw_replstatus pgmeminfo toastinfo pg_explain_ui pg_relusage pagevis powa
          - passwordcheck supautils pgsodium pg_vault pg_session_jwt pg_anon pg_tde pgsmcrypto pgaudit pgauditlogtofile pg_auth_mon credcheck pgcryptokey pg_jobmon logerrors login_hook set_user pg_snakeoil pgextwlist pg_auditor sslutils pg_noset
          - wrappers multicorn odbc_fdw jdbc_fdw mysql_fdw tds_fdw sqlite_fdw pgbouncer_fdw mongo_fdw redis_fdw pg_redis_pubsub kafka_fdw hdfs_fdw firebird_fdw aws_s3 log_fdw #oracle_fdw #db2_fdw
          - documentdb orafce pgtt session_variable pg_statement_rollback pg_dbms_metadata pg_dbms_lock pgmemcache #pg_dbms_job #wiltondb
          - pglogical pglogical_ticker pgl_ddl_deploy pg_failover_slots db_migrator wal2json wal2mongo decoderbufs decoder_raw mimeo pg_fact_loader pg_bulkload #repmgr

Or install all extensions by category aliases globally:

all:
  vars:
    pg_version: 18   # default in v3.7, so pgsql-main is equivalent to pg18-main
    pg_extensions: [ pgsql-main ,pgsql-time ,pgsql-gis ,pgsql-rag ,pgsql-fts ,pgsql-olap ,pgsql-feat ,pgsql-lang ,pgsql-type ,pgsql-util ,pgsql-func ,pgsql-admin ,pgsql-stat ,pgsql-sec ,pgsql-fdw ,pgsql-sim ,pgsql-etl]

You can also specify the PG major version explicitly in these alias:

all:
  vars:

    pg_extensions: [pg17-time ,pg17-gis ,pg17-rag ,pg17-fts ,pg17-feat ,pg17-lang ,pg17-type ,pg17-util ,pg17-func ,pg17-admin ,pg17-stat ,pg17-sec ,pg17-fdw ,pg17-sim ,pg17-etl ] #,pg17-olap]

Install all extensions simultaneously is applicable (except two conflicts in the olap category) but not recommended. Just install the extensions you need by explicitly specifying them in the pg_extensions parameter.


Configure

During PGSQL cluster init, Pigsty will automatically install packages (& alias) specified in pg_packages and pg_extensions.

Both parameters can be used to install PostgreSQL-related packages. Typically, pg_packages is used to globally specify packages that should be installed across all PostgreSQL clusters in your environment: such as the PostgreSQL kernel, high-availability agent like Patroni, connection pooling with pgBouncer, monitoring with pgExporter, etc.

By default, Pigsty also specifies 3 important extensions here: pgvector, pg_repack, and wal2json for vector search, bloat management, and CDC change extraction.

Meanwhile, pg_extensions is usually used to specify extension for a specific cluster. The default is an empty list, indicating no other extensions will be installed by default.

pg_packages:                      # pg packages to be installed, alias can be used, state=present
  - postgresql
  - wal2json pg_repack pgvector
  - patroni pgbouncer pgbackrest pg_exporter pgbadger vip-manager
pg_extensions: []                 # pg extensions to be installed, alias can be used, state=latest

An important distinction: packages installed via pg_packages are merely ensured to be present, whereas those installed via pg_extensions are automatically upgraded to the latest available version.

When using a local software repo, this distinction isn’t an issue. However, when using upstream internet repo, consider this carefully and move extensions you don’t want automatically upgraded to pg_packages.


Install

Extensions pre-defined in the pg_extensions (and pg_packages) will be installed during cluster provisioning.

To install new extensions on a provisioned PostgreSQL cluster:

First, add extensions to pg_extensions, then execute the playbook subtask:

./pgsql.yml -t pg_extension  # install extensions specified in pg_extensions

Note that extension plugins specified in the pg_extension task will be upgraded to the latest available version in your current environment by default.


Repo

To install extension, you need to ensure one of the following conditions is met:

  • Local Repo: You have configured using Pigsty’s local repo, and the extensions have already been downloaded to the local repo.
  • Online Repo: You have directly configured upstream internet repo on the target node, and internet access is available on these nodes.

For production environments, we recommend using Pigsty’s local software repo to manage and install extensions uniformly: First download extensions to the local repo, then install them from there. This ensures consistent extension versions across your environment and prevents database nodes from directly accessing the internet. You have to do nothing when installed from local repo, just make sure they are downloaded to the local repo.

For development environments, you may choose to directly use upstream internet repo for convenience. Use the following commands to add Internet repo and install extensions on the target cluster directly:

./node.yml  -l <cls> -t node_repo -e node_repo_modules=local,node,pgsql    # Enable internet repo on target node
./pgsql.yml -l <cls> -t pg_extension                                        # Install extensions using local+internet upstream repos

Package Alias

When installing extensions, users can use extension aliases to specify extension.

The aliases will be translated to the current active PG major version and OS environment.

and translated to the corresponding RPM/DEB package names by alias translation mechanism.


Caveats

  • There are two known conflicts:
  • pgaudit got a different naming pattern on el for pg 15-: pg16+ = pgaudit, pg15=pgaudit17, pg14=pgaudit16 pg13=pgaudit15 pg12=pgaudit14
  • postgis got its own version in el package name: postgis35 by default, and postgis33 for legacy el7

17.5 - Config

Preload extensions and configure extension parameters

While most PostgreSQL extensions written in SQL can be directly enabled with CREATE EXTENSION, some extensions that use special postgres hook will require an extra step to preload them before using.


Preloading

Most extensions have one or more corresponding dynamic library (.so, .dylib, .dll), some of them require preloading before using. Attempting to CREATE these extensions without proper preloading will result in an error. And a wrongly configured preload library may lead to a failure on database restart/start.

Some extensions can partially work without preloading, which means part of the extension features are available directly, and the rest of the features are available after preloading.

To preload an extension, add it to the shared_preload_libraries and restart the database server. The Extension Catalog gives the complete list of extensions that require dynamic preloading.


Configure

To configure a preload on new postgres cluster, the pg_libs parameter can be used. It will be populated to the shared_preload_libraries parameter during postgres cluster bootstrap.

Example: Setup Supabase Extension Preloading

This example show how to specify pre-loaded extensions with pg_libs parameter.

all:
  children:
pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta
    pg_libs: 'timescaledb, plpgsql, plpgsql_check, pg_cron, pg_net, pg_stat_statements, auto_explain, pg_tle, plan_filter'

shared_preload_libraries is a comma-separated list of extensions.

Beware that only works before cluster creation. After that, you’ll have to config cluster to change the shared_preload_libraries parameter on existing cluster. (with patronictl, ALTER SYSTEM, etc…)

add timescaledb to shared_preload_libraries
pg edit-config pg-meta --force -p shared_preload_libraries='timescaledb, pg_stat_statements, auto_explain'
pg restart pg-meta    # restart to apply changes

If you want to configure preloading manually, you can just change the postgresql.conf by yourself


Default

The default value of pg_libs is pg_stat_statements, auto_explain, which preload these two Contrib extensions by default, these two extensions provide essential observability:

  • auto_explain: Automatic logging of slow query execution plans
  • pg_stat_statements: Tracks planning and execution statistics for grouped SQL statements

Caveats

Preload libraries are loaded one by one, so the order of extensions in shared_preload_libraries matters, Here are some known rules to follow:

  • For STAT extension, add them AFTER pg_stat_statements to ensure using the same query_id.
  • timescaledb and citus should be placed at the BEGINNING of shared_preload_libraries
  • If you use citus and timescaledb together, place citus before timescaledb.
  • Use pg_documentdb and pg_documentdb_core as library name for documentdb.
  • pg_search does not require preloading in PostgreSQL 17 and later, but earlier versions do.

Parameter

Some extensions have configurable parameters, you can manage them in different places.

Consult the official docs of each extension for details.

17.6 - Create

Create & Enable PostgreSQL Extension

Quick Start

You can enable (create) extension using the CREATE EXTENSION statement:

CREATE EXTENSION vector; -- no explicit loading required
CREATE EXTENSION timescaledb; -- explicit loading required

Extensions need to be installed first, some extension also requires preloading before using.

Some extensions have dependencies on other extensions. In such cases, you can either install the dependencies first or use the CASCADE clause to install all dependencies at once.

CREATE EXTENSION documentdb CASCADE; -- create documentdb extension and all its dependencies

You can also provision extension with Pigsty, which will automatically create the extensions for you.


Configure

Extensions (database logical objects) are logically part of PostgreSQL databases. In Pigsty, you can specify which extensions to be created in a database with pg_databases parameter.

pg_databases:
  - { name: meta ,extensions: [ vector, postgis, timescaledb ] }

But you can explicitly specify extension details with the object format, like create them in a specific schema. Or install a specific version. Here’s a complete example (self-hosting supabase):

pg_databases:
  - name: postgres
    baseline: supabase.sql
    schemas: [ extensions ,auth ,realtime ,storage ,graphql_public ,supabase_functions ,_analytics ,_realtime ]
    extensions:                                 # Extensions to be enabled in the postgres database
      - { name: pgcrypto  ,schema: extensions } # cryptographic functions
      - { name: pg_net    ,schema: extensions } # async HTTP
      - { name: pgjwt     ,schema: extensions } # json web token API for postgres
      - { name: uuid-ossp ,schema: extensions } # generate universally unique identifiers (UUIDs)
      - { name: pgsodium        }               # pgsodium is a modern cryptography library for Postgres.
      - { name: supabase_vault  }               # Supabase Vault Extension
      - { name: pg_graphql      }               # pg_graphql: GraphQL support
      - { name: pg_jsonschema   }               # pg_jsonschema: Validate json schema
      - { name: wrappers        }               # wrappers: FDW collections
      - { name: http            }               # http: allows web page retrieval inside the database.
      - { name: pg_cron         }               # pg_cron: Job scheduler for PostgreSQL
      - { name: timescaledb     }               # timescaledb: Enables scalable inserts and complex queries for time-series data
      - { name: pg_tle          }               # pg_tle: Trusted Language Extensions for PostgreSQL
      - { name: vector          }               # pgvector: the vector similarity search
      - { name: pgmq            }               # pgmq: A lightweight message queue like AWS SQS and RSMQ

Define Extension

The extensions field is a list of extension (name or object) to be created in the database. It will be created under the first schema in dbsu’s search_path, (usually the public schema).

Here, the extensions in the database object is a list where each element can be:

  • A simple string representing the extension name, such as vector
  • Alternatively, A dictionary that may contain the following fields can be used:
    • name: Extension name, REQUIRED, beware it may differ from the extension package name.
    • schema: Schema for installing the extension, OPTIONAL, defaults to the first schema in the current dbsu search path, usually the default public.
    • version: Specifies the extension version, OPTIONAL, defaults to the latest version, rarely used.

If the database doesn’t exist yet, the extensions defined here will be automatically created when creating a cluster or creating a database through Pigsty.

Re-creating database with non-trivial baseline schema may be dangerous (if you put some DROP there) So for existing clusters / databases, it’s advised to use your own schema migration tool to manage extensions. (pgadmin, psql, bytebase, flyway, sqlitch,…) But it’s helpful to enlist them in the config inventory for bookkeeping purposes. (So if you want to fork this cluster, it includes these extensions)


Default Extension

Some built-in extensions and one special pg_repack are created by default in Pigsty.

These extensions are defined by pg_default_extensions, created in the template1 database and the postgres database by default. Newly created databases will inherit these extensions from template1, so you don’t need to create them again.

pg_default_extensions:
  - { name: pg_stat_statements ,schema: monitor }
  - { name: pgstattuple        ,schema: monitor }
  - { name: pg_buffercache     ,schema: monitor }
  - { name: pageinspect        ,schema: monitor }
  - { name: pg_prewarm         ,schema: monitor }
  - { name: pg_visibility      ,schema: monitor }
  - { name: pg_freespacemap    ,schema: monitor }
  - { name: postgres_fdw       ,schema: public  }
  - { name: file_fdw           ,schema: public  }
  - { name: btree_gist         ,schema: public  }
  - { name: btree_gin          ,schema: public  }
  - { name: pg_trgm            ,schema: public  }
  - { name: intagg             ,schema: public  }
  - { name: intarray           ,schema: public  }
  - { name: pg_repack } # <-- The only 3rd-party extension created by default

One extra default schema monitor is defined by pg_default_schemas is also created by default. Which is used to contain monitoring related extensions, tables, functions and views.

There are three 3rd-party extensions that are available by default in Pigsty:

Extension What Where
pg_repack Online Bloat Control Tools in the pg_default_extensions
wal2json Changing data capture in JSON extension without DDL, install means available
vector vector data type & indexes in pg_databases as an example

The pg_repack extension is an important utility for maintaining bloat tables online.

vector is a very popular extension for RAG, It is installed by default (in the pgsql-main alias) and created in the placeholder meta database in most config template.

The wal2json is another important extension for Changing Data Capture (CDC). It is installed by default, but it is an extension without DDL, So you don’t need to CREATE it explicitly.


Extension without DDL

Extension without DDL does not require the CREATE EXTENSION command to work

PostgreSQL extensions typically consist of three parts: a required control file, optional SQL files, and optional libraries. If an extension does not have SQL file, CREATE EXTENSION command is not needed.

Component Description Required
Control file Key metadata, name, dependencies, schema, version,… REQUIRED
SQL file SQL DDL statements, Types, Functions, etc… OPTIONAL
Library file binary shared libraries (.so, .dylib, .dll) OPTIONAL

Since SQL / LIB files are optional, there are four possible combinations of extension types:

LOAD / DDL Requires CREATE EXTENSION Doesn’t require CREATE EXTENSION
Requires LOAD Extensions using hooks Headless extensions
Doesn’t Require LOAD Extensions not using hooks Logical decoding output plugins

17.7 - Update

How to update PostgreSQL extensions to newer versions

To update an existing extension, you need to first update the RPM/DEB package with your OS’s package manager, then alter the extension to the new version in PostgreSQL with ALTER EXTENSION ... UPDATE.

You can upgrade extension packages with the following commands

pig ext update extname...
yum upgrade extname...
apt upgrade extname...
./pgsql.yml -t pg_ext   # -l cls

All extensions listed in pg_extensions will be upgraded using during the pgsql.yml playbook execution.


Upgrade Packages

Extensions (Package Alias) listed in pg_extensions will be upgraded with pgsql.yml’s pg_ext subtask:

~/pigsty
./pgsql.yml -t pg_ext

This playbook will automatically install the latest available version of extension RPM/DEB packages in your current environment. (from built local repo or via Internet directly). You can also upgrade extensions with linux system’s yum/apt upgrade command directly, but you need to specify the full package names:

yum upgrade extname...
apt upgrade extname...

Pigsty’s pig cli can also help you with that, without the burden of specifying full package names:

pig ext update extname|pkgalias

Alter Extension

Execute the ALTER EXTENSION ... UPDATE SQL command to update the extension to the new version:

ALTER EXTENSION name UPDATE [ TO new_version ]

If the TO new_version clause is omitted, the extension will be updated to the latest version available.

17.8 - Remove

How to remove PostgreSQL extensions

Remove Extension

To uninstall an extension, you typically need to run the DROP EXTENSION SQL statement:

DROP EXTENSION "<extname>";

If other extensions or database objects depend on this extension, you’ll need to remove those dependencies first before uninstalling the extension. Or remove all of them with CASCADE option:

DROP EXTENSION "<extname>" CASCADE;
Warning

The CASCADE option will delete all objects that depend on this extension,
including database objects, functions, views, etc. Use with caution!

Some extensions don’t have DDL, these extensions do not require the DROP EXTENSION statement to uninstall. Instead, you can simply remove the extension from the shared_preload_libraries (if configured) and uninstall the package. Refer to the Extensions Without DDL section for more details.


Remove Loading

If you’re using an extension that requires dynamic loading (which modifies the shared_preload_libraries parameter), you need to first re-confnigure the shared_preload_libraries parameter.

Remove the extension name from shared_preload_libraries and restart the database cluster for the changes to take effect.

For extensions that need dynamic loading, refer to the Extensions that Need Loading list.


Uninstall Package

After removing the extension (logical object) from all databases in the cluster, you can safely uninstall the extension’s software package. Ansible commands can help you do this conveniently:

ansible <cls> -m package -a "name=<extname> state=absent"

You can also use pig, or apt/yum commands directly to uninstall.

If you don’t know the extension package name, you can refer to the Extension List or check the extension package name mapping defined in roles/node_id/vars.