Create 3 servers on the cloud of choice.

This is a diagram of the servers:

servers

Node 1

On Node 1 we install postgres, start the postgresql service and add the port to firewall:

sudo apt install -y postgresql
sudo systemctl enable postgresql
sudo systemctl start postgresql
sudo systemctl status postgresql
sudo ufw allow from 10.0.0.0/16 to any port 5432

Next, we open psql to create the replication user:

sudo -u postgres psql
CREATE USER ruser REPLICATION LOGIN CONNECTION LIMIT 3 ENCRYPTED PASSWORD 'rpassword';
CREATE USER dummy WITH SUPERUSER PASSWORD '123456';

We should open /etc/postgresql/14/main/pg_hba.conf and add the users:

host    replication     ruser           10.0.0.0/16             md5
host    all             dummy           10.0.0.0/16             md5

Now we should edit /etc/postgresql/14/main/postgresql.conf for replication:

listen_addresses = 'localhost,10.0.0.3'     # what IP address(es) to listen on;
                                  # comma-separated list of addresses;
                                  # defaults to 'localhost'; use '*' for all
                                  # (change requires restart)
port = 5432        # (change requires restart)
max_connections = 500     # (change requires restart)
wal_level = replica         # minimal, replica, or logical
             # (change requires restart)
wal_log_hints = on          # also do full page writes of non-critical updates
             # (change requires restart)
wal_compression = on        # enable compression of full-page writes
max_wal_size = 1GB
min_wal_size = 80MB
max_wal_senders = 10        # max number of walsender processes
                            # (change requires restart)
shared_preload_libraries = 'pg_stat_monitor'
pg_stat_monitor.pgsm_query_max_len = 2048
pg_stat_monitor.pgsm_normalized_query = 1
pg_stat_monitor.pgsm_enable_query_plan = yes

Now, to install Percona pg_stat_monitor:

curl -O https://repo.percona.com/apt/percona-release_latest.generic_all.deb
sudo apt install gnupg2 lsb-release ./percona-release_latest.generic_all.deb
sudo apt update
sudo percona-release setup ppg-14
sudo apt install percona-pg-stat-monitor14

Next we restart postgresql.

sudo systemctl restart postgresql

Node 2

On Node 2 we install postgres, start the postgresql service and add the port to firewall:

sudo apt install -y postgresql
sudo systemctl enable postgresql
sudo systemctl start postgresql
sudo systemctl status postgresql
sudo ufw allow from 10.0.0.0/16 to any port 5432

Next, we open psql to create the replication user:

sudo -u postgres psql
CREATE USER ruser REPLICATION LOGIN CONNECTION LIMIT 3 ENCRYPTED PASSWORD 'rpassword';
CREATE USER dummy WITH SUPERUSER PASSWORD '123456';

We should open /etc/postgresql/14/main/pg_hba.conf and add the users:

host    replication     ruser           10.0.0.0/16             md5
host    all             dummy           10.0.0.0/16              md5

Now we should edit /etc/postgresql/14/main/postgresql.conf for replication:

listen_addresses = 'localhost,10.0.0.4'     # what IP address(es) to listen on;
                                  # comma-separated list of addresses;
                                  # defaults to 'localhost'; use '*' for all
                                  # (change requires restart)
port = 5432        # (change requires restart)
max_connections = 500     # (change requires restart)
wal_level = replica         # minimal, replica, or logical
             # (change requires restart)
wal_log_hints = on          # also do full page writes of non-critical updates
             # (change requires restart)
wal_compression = on        # enable compression of full-page writes
max_wal_size = 1GB
min_wal_size = 80MB
max_wal_senders = 10        # max number of walsender processes
                            # (change requires restart)
shared_preload_libraries = 'pg_stat_monitor'
pg_stat_monitor.pgsm_query_max_len = 2048
pg_stat_monitor.pgsm_normalized_query = 1
pg_stat_monitor.pgsm_enable_query_plan = yes

Now, to install Percona pg_stat_monitor:

curl -O https://repo.percona.com/apt/percona-release_latest.generic_all.deb
sudo apt install gnupg2 lsb-release ./percona-release_latest.generic_all.deb
sudo apt update
sudo percona-release setup ppg-14
sudo apt install percona-pg-stat-monitor14

Next we restart postgresql to check the updated settings

sudo systemctl restart postgresql

Now, let’s find out the data dir for postgres:

sudo -u postgres psql

SHOW data_directory;
       data_directory
-----------------------------
 /var/lib/postgresql/14/main

If everything is ok, we should stop postgresql on node 2 and prepare the replication:

sudo systemctl stop postgresql
sudo cp -pr /var/lib/postgresql/14/main /var/lib/postgresql/14/main_orig
sudo rm -rf /var/lib/postgresql/14/main
sudo mkdir /var/lib/postgresql/14/main
sudo chmod -R 775 /var/lib/postgresql/14/main

We should backup node 1:

sudo pg_basebackup -h 10.0.0.3 -D /var/lib/postgresql/14/main -U ruser -P -v -R -X stream
sudo chown -R postgres:postgres /var/lib/postgresql/14/main
sudo chmod -R 700 /var/lib/postgresql/14/main

If everything is’s ok, we should backup the configuration and start postgresql:

sudo systemctl start postgresql

To check if the replication is running, we open psql:

sudo -u postgres psql
SELECT * FROM pg_stat_wal_receiver;

And if we have 1 row, everything is ok.

Master

Now, let’s install pgpool2 and allow port 5432 on the firewall:

sudo apt install pgpool2 postgresql-client
sudo ufw allow 5432

Let’s edit the configuration file /etc/pgpool2/pgpool.conf:

#------------------------------------------------------------------------------
# CONNECTIONS
#------------------------------------------------------------------------------
# - pgpool Connection Settings -
listen_addresses = '192.168.10.10'
                                   # Host name or IP address to listen on:
                                   # '*' for all, '' for no TCP/IP connections
                                   # (change requires restart)
port = 5432
                                   # Port number
                                   # (change requires restart)
socket_dir = '/var/run/postgresql'
                                   # Unix domain socket path
                                   # The Debian package defaults to
                                   # /var/run/postgresql
                                   # (change requires restart)
listen_backlog_multiplier = 2
                                   # Set the backlog parameter of listen(2) to
                                   # num_init_children * listen_backlog_multiplier.
                                   # (change requires restart)
serialize_accept = off
                                   # whether to serialize accept() call to avoid thundering herd problem
                                   # (change requires restart)
reserved_connections = 0
                                   # Number of reserved connections.
                                   # Pgpool-II does not accept connections if over
                                   # num_init_chidlren - reserved_connections.
# - pgpool Communication Manager Connection Settings -
pcp_listen_addresses = '*'
                                   # Host name or IP address for pcp process to listen on:
                                   # '*' for all, '' for no TCP/IP connections
                                   # (change requires restart)
pcp_port = 9898
                                   # Port number for pcp
                                   # (change requires restart)
pcp_socket_dir = '/var/run/postgresql'
                                   # Unix domain socket path for pcp
                                   # The Debian package defaults to
                                   # /var/run/postgresql
                                   # (change requires restart)
# - Backend Connection Settings -
backend_hostname0 = '10.0.0.3'
                                   # Host name or IP address to connect to for backend 0
backend_port0 = 5432
                                   # Port number for backend 0
backend_weight0 = 1
                                   # Weight for backend 0 (only in load balancing mode)
backend_data_directory0 = '/var/lib/postgresql/main'
                                   # Data directory for backend 0
backend_flag0 = 'ALLOW_TO_FAILOVER'
                                   # Controls various backend behavior
                                   # ALLOW_TO_FAILOVER, DISALLOW_TO_FAILOVER
                                   # or ALWAYS_MASTER
backend_application_name0 = 'postgresql-master.internal'
                                   # walsender's application_name, used for "show pool_nodes" command
backend_hostname1 = '10.0.0.4'
backend_port1 = 5432
backend_weight1 = 1
backend_data_directory1 = '/var/lib/postgresql/standby'
backend_flag1 = 'ALLOW_TO_FAILOVER'
backend_application_name1 = 'postgresql-slave01.internal'
# - Authentication -
enable_pool_hba = on
                                   # Use pool_hba.conf for client authentication
pool_passwd = 'pool_passwd'
                                   # File name of pool_passwd for md5 authentication.
                                   # "" disables pool_passwd.
                                   # (change requires restart)
authentication_timeout = 60
                                   # Delay in seconds to complete client authentication
                                   # 0 means no timeout.
allow_clear_text_frontend_auth = on
                                   # Allow Pgpool-II to use clear text password authentication
                                   # with clients, when pool_passwd does not
                                   # contain the user password
#------------------------------------------------------------------------------
# POOLS
#------------------------------------------------------------------------------
# - Concurrent session and pool size -
num_init_children = 100            # Number of concurrent sessions allowed
                                   # (change requires restart)
max_pool = 10                      # Number of connection pool caches per connection
                                   # (change requires restart)
# - Life time -
child_life_time = 300
                                   # Pool exits after being idle for this many seconds
child_max_connections = 0
                                   # Pool exits after receiving that many connections
                                   # 0 means no exit
connection_life_time = 0
                                   # Connection to backend closes after being idle for this many seconds
                                   # 0 means no close
client_idle_limit = 0
                                   # Client is disconnected after being idle for that many seconds
                                   # (even inside an explicit transactions!)
                                   # 0 means no disconnection
#------------------------------------------------------------------------------
# CONNECTION POOLING
#------------------------------------------------------------------------------
connection_cache = on
                                   # Activate connection pools
                                   # (change requires restart)
                                   # Semicolon separated list of queries
                                   # to be issued at the end of a session
                                   # The default is for 8.3 and later
reset_query_list = 'ABORT; DISCARD ALL'

#------------------------------------------------------------------------------
# REPLICATION MODE
#------------------------------------------------------------------------------
replication_mode = off
                                   # Activate replication mode
                                   # (change requires restart)
replicate_select = off
                                   # Replicate SELECT statements
                                   # when in replication mode
                                   # replicate_select is higher priority than
                                   # load_balance_mode.
insert_lock = on
                                   # Automatically locks a dummy row or a table
                                   # with INSERT statements to keep SERIAL data
                                   # consistency
                                   # Without SERIAL, no lock will be issued
lobj_lock_table = ''
                                   # When rewriting lo_creat command in
                                   # replication mode, specify table name to
                                   # lock
# - Degenerate handling -
replication_stop_on_mismatch = off
                                   # On disagreement with the packet kind
                                   # sent from backend, degenerate the node
                                   # which is most likely "minority"
                                   # If off, just force to exit this session
failover_if_affected_tuples_mismatch = off
                                   # On disagreement with the number of affected
                                   # tuples in UPDATE/DELETE queries, then
                                   # degenerate the node which is most likely
                                   # "minority".
                                   # If off, just abort the transaction to
                                   # keep the consistency

#------------------------------------------------------------------------------
# LOAD BALANCING MODE
#------------------------------------------------------------------------------
load_balance_mode = on
                                   # Activate load balancing mode
                                   # (change requires restart)
ignore_leading_white_space = on
                                   # Ignore leading white spaces of each query
white_function_list = ''
                                   # Comma separated list of function names
                                   # that don't write to database
                                   # Regexp are accepted
black_function_list = 'currval,lastval,nextval,setval'
                                   # Comma separated list of function names
                                   # that write to database
                                   # Regexp are accepted
black_query_pattern_list = ''
                                   # Semicolon separated list of query patterns
                                   # that should be sent to primary node
                                   # Regexp are accepted
                                   # valid for streaming replicaton mode only.
database_redirect_preference_list = ''
                                   # comma separated list of pairs of database and node id.
                                   # example: postgres:primary,mydb[0-4]:1,mydb[5-9]:2'
                                   # valid for streaming replicaton mode only.
app_name_redirect_preference_list = ''
                                   # comma separated list of pairs of app name and node id.
                                   # example: 'psql:primary,myapp[0-4]:1,myapp[5-9]:standby'
                                   # valid for streaming replicaton mode only.
allow_sql_comments = off
                                   # if on, ignore SQL comments when judging if load balance or
                                   # query cache is possible.
                                   # If off, SQL comments effectively prevent the judgment
                                   # (pre 3.4 behavior).
disable_load_balance_on_write = 'transaction'
                                   # Load balance behavior when write query is issued
                                   # in an explicit transaction.
                                   # Note that any query not in an explicit transaction
                                   # is not affected by the parameter.
                                   # 'transaction' (the default): if a write query is issued,
                                   # subsequent read queries will not be load balanced
                                   # until the transaction ends.
                                   # 'trans_transaction': if a write query is issued,
                                   # subsequent read queries in an explicit transaction
                                   # will not be load balanced until the session ends.
                                   # 'always': if a write query is issued, read queries will
                                   # not be load balanced until the session ends.
statement_level_load_balance = off
                                   # Enables statement level load balancing
#------------------------------------------------------------------------------
# MASTER/SLAVE MODE
#------------------------------------------------------------------------------
master_slave_mode = on
                                   # Activate master/slave mode
                                   # (change requires restart)
master_slave_sub_mode = 'stream'
                                   # Master/slave sub mode
                                   # Valid values are combinations stream, slony
                                   # or logical. Default is stream.
                                   # (change requires restart)
# - Streaming -
sr_check_period = 0
                                   # Streaming replication check period
                                   # Disabled (0) by default
sr_check_user = 'ruser'
                                   # Streaming replication check user
                                   # This is necessary even if you disable
                                   # streaming replication delay check with
                                   # sr_check_period = 0
sr_check_password = 'rpassword'
                                   # Password for streaming replication check user.
                                   # Leaving it empty will make Pgpool-II to first look for the
                                   # Password in pool_passwd file before using the empty password
sr_check_database = 'postgres'
                                   # Database name for streaming replication check
delay_threshold = 0
                                   # Threshold before not dispatching query to standby node
                                   # Unit is in bytes
                                   # Disabled (0) by default
#------------------------------------------------------------------------------
# HEALTH CHECK GLOBAL PARAMETERS
#------------------------------------------------------------------------------
health_check_period = 0
                                   # Health check period
                                   # Disabled (0) by default
health_check_timeout = 20
                                   # Health check timeout
                                   # 0 means no timeout
health_check_user = 'ruser'
                                   # Health check user
health_check_password = 'rpassword'
                                   # Password for health check user
                                   # Leaving it empty will make Pgpool-II to first look for the
                                   # Password in pool_passwd file before using the empty password
health_check_database = ''
                                   # Database name for health check. If '', tries 'postgres' frist, then 'template1'
health_check_max_retries = 0
                                   # Maximum number of times to retry a failed health check before giving up.
health_check_retry_delay = 1
                                   # Amount of time to wait (in seconds) between retries.
connect_timeout = 10000
                                   # Timeout value in milliseconds before giving up to connect to backend.
                                   # Default is 10000 ms (10 second). Flaky network user may want to increase
                                   # the value. 0 means no timeout.
                                   # Note that this value is not only used for health check,
                                   # but also for ordinary conection to backend.

Next, we should generate a secret for password hashing:

openssl rand -base64 32 > /home/user/.pgpoolkey
sudo cp /home/user/.pgpoolkey /var/lib/postgresql/
sudo chown postgres:postgres /var/lib/postgresql/.pgpoolkey
sudo chmod 600 /var/lib/postgresql/.pgpoolkey

Now let’s edit /etc/pgpool2/pool_hba.conf and add our users:

host	all	    ruser	all	      scram-sha-256
host	all	    dummy	all	      scram-sha-256

Running to register username with sha25sum:

sudo pg_enc -m -k /var/lib/postgresql/.pgpoolkey -f /etc/pgpool2/pgpool.conf -u ruser -p
sudo pg_enc -m -k /var/lib/postgresql/.pgpoolkey -f /etc/pgpool2/pgpool.conf -u dummy -p

Finally let’s start pgpool2:

sudo systemctl start pgpool2