PostgreSQL 10 Streaming Replication on Ubuntu 18.04
The following article will walk through setting up PostgreSQL 10 on Ubuntu 18.04. We’ll set up three nodes, one leader and a pair of followers (replica). For the nodes, I’ll just spin up some Droplets on DigitalOcean. For the purposes of this article, I’ll leave configuration management out and we’ll do it by hand.
Let’s spin up some Droplets:
❯ for i in 1 2 3; do
doctl compute droplet create \
--image ubuntu-18-04-x64 --size s-2vcpu-4gb \
--region nyc1 db${i} --ssh-keys <my-ssh-key-id>
done
After a few minutes, we’ll be ready to go:
❯ doctl compute droplet list | awk '{print $2" "$3}'
Name Public
db1 167.172.155.245
db2 161.35.13.16
db3 161.35.62.178
Get a root shell and update the APT cache on all of them with apt update
.
On the primary, db1
, go ahead and apt install -y postgresql-10
.
After a few minutes or so, you should have a “cluster” named main
running version 10
.
You can see this with:
root@db1:~# pg_lsclusters
Ver Cluster Port Status Owner Data directory Log file
10 main 5432 online postgres /var/lib/postgresql/10/main /var/log/postgresql/postgresql-10-main.log
There are systemd
unit files as well for the service:
root@db1:~# systemctl list-units | grep postgres | grep service
postgresql.service loaded active exited PostgreSQL RDBMS
postgresql@10-main.service loaded active running PostgreSQL Cluster 10-main
Now, you should be able to poke around inside as the postgres
user like so:
root@db1:~# su - postgres -c psql
psql (10.15 (Ubuntu 10.15-0ubuntu0.18.04.1))
Type "help" for help.
postgres=# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+---------+---------+-----------------------
postgres | postgres | UTF8 | C.UTF-8 | C.UTF-8 |
template0 | postgres | UTF8 | C.UTF-8 | C.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | C.UTF-8 | C.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(3 rows)
Cool, let’s get Postgres installed on the other two nodes with apt install -y postgresql-10
as well.
Since they’re going to be followers (replica) nodes, we’ll nuke the database files afterwards.
While the other two nodes are installing Postgres, we can fixup a few settings on the primary.
Out of the box, Postgres only listens on loopback, so, let’s set the listen_addresses
parameter in the main Postgres configuration file to *
, which means “listen on all interfaces”.
To get their initial backups, as well as subsequent streaming replication, the followers need to connect to the Postgres daemon on the leader.
On Bionic, the main configuration file for Postgres is located at /etc/postgresql/10/main/postgresql.conf
.
Here’s the before (notice the 127.0.0.1:5432
):
root@db1:~# ss -nltp | grep 5432
LISTEN 0 128 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=3322,fd=7))
After fixing up the configuration file like so:
root@db1:~# ss -nltp | grep 5432
LISTEN 0 128 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=3322,fd=7))
Let’s restart and check again:
root@db1:~# systemctl restart postgresql; ss -nltp | grep 5432
LISTEN 0 128 0.0.0.0:5432 0.0.0.0:* users:(("postgres",pid=15712,fd=7))
LISTEN 0 128 [::]:5432 [::]:* users:(("postgres",pid=15712,fd=8))
Cool, the followers should at least be able to talk to our leader at this point.
Next, let’s create a role in Postgres to be used for replication:
root@db1:~# su - postgres -c psql
psql (10.15 (Ubuntu 10.15-0ubuntu0.18.04.1))
Type "help" for help.
postgres=# create role replication login replication encrypted password 'hunter2';
CREATE ROLE
Next, we need to add a few of rules to Postgres’ hba (host-based authentication) file as such:
root@db1:~# tail -4 /etc/postgresql/10/main/pg_hba.conf
host all replication 161.35.13.16/32 md5
host all replication 161.35.62.178/32 md5
host replication replication 161.35.13.16/32 md5
host replication replication 161.35.62.178/32 md5
This is a whitespace-delimited file, the first column represents the type of authentication.
The second column is the database which can be accessed.
The third column is the user/role (notice it matches the role we created).
The fourth column is the address (these are the IP addresses of my follower Droplets).
The last column is the type of authentication.
There’s lots of good information in the comments of /etc/postgresql/10/main/pg_hba.conf
, please check it out.
Cool, let’s try connecting to our leader from one of the followers:
root@db2:~# psql -U replication -h 167.172.155.245 -p 5432 -W postgres
Password for user replication:
psql: FATAL: no pg_hba.conf entry for host "161.35.13.16", user "replication", database "postgres", SSL on
FATAL: no pg_hba.conf entry for host "161.35.13.16", user "replication", database "postgres", SSL of
Ah, we need to reload.
We can do this as we did before, with systemctl restart postgresql
.
Or, we can do pg_ctlcluster 10 main reload
.
Or, we can do it like this:
postgres=# select pg_reload_conf();
pg_reload_conf
----------------
t
(1 row)
After reloading Postgres, we should be able to get in:
root@db2:~# psql -U replication -h 167.172.155.245 -p 5432 -W postgres
Password for user replication:
psql (10.15 (Ubuntu 10.15-0ubuntu0.18.04.1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
postgres=>
Might as well make sure we can do it from the other follower:
root@db3:~# psql -U replication -h 167.172.155.245 -p 5432 -W postgres
Password for user replication:
psql (10.15 (Ubuntu 10.15-0ubuntu0.18.04.1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
postgres=>
Cool, we have network connectivity, and, application-level connectivity.
Alright, let’s stop the database and nuke the data directory on the first follower.
We could use pg_dropcluster 10 main
, but, that’d get rid of the configuration files, and we’d have to recreate them.
Doing it this way is fast and easy, we’re basically leaving “all but the data” around, and this is exactly what we want for a follower (we’ll stream a backup from the primary shortly).
Don’t forget to stop Postgres first so that files aren’t in use.
root@db2:~# systemctl stop postgresql; rm -fr /var/lib/postgresql/10/main/*
root@db2:~# pg_lsclusters
Ver Cluster Port Status Owner Data directory Log file
10 main 5432 down postgres /var/lib/postgresql/10/main /var/log/postgresql/postgresql-10-main.log
Alright, let’s create a replication slot on the primary:
postgres=# select * from pg_create_physical_replication_slot('db2');
slot_name | lsn
-----------+-----
db2 |
(1 row)
Let’s make sure a few WAL (write-ahead log) and replication directives are set on the primary:
wal_level = replica
archive_mode = on
max_wal_senders = 10
wal_keep_segments = 32
max_replication_slots = 10
hot_standby = on
hot_standby_feedback = on
If you needed to set/change any of these, don’t forget to restart/reload Postgres.
Also, this is by no means “ready for production”, it’s just a PoC.
Specifically, note that I’m setting archive_mode
, but, I’m not mentioning archive_command
.
I’m going to do that annoying “this exercise is left for the reader”.
As an example, you could archive your WALs with rsync
to a shared location, you could use NFS, you could archive them in Spaces, etc.
Cool, let’s stream a backup now from the primary onto the first follower (make sure you’re the postgres
user):
postgres@db2:~$ pg_basebackup -h 167.172.155.245 -p 5432 -U replication -D /var/lib/postgresql/10/main --progress -W --slot db2 --wal-method=stream --write-recovery-conf
Password:
23663/23663 kB (100%), 1/1 tablespace
postgres@db2:~$ pg_ctlcluster 10 main start
Warning: the cluster will not be running as a systemd service. Consider using systemctl:
sudo systemctl start postgresql@10-main
postgres@db2:~$ pg_lsclusters
Ver Cluster Port Status Owner Data directory Log file
10 main 5432 online,recovery postgres /var/lib/postgresql/10/main /var/log/postgresql/postgresql-10-main.log
Cool, notice that in addition to online
there’s also a status of type recovery
.
So, check this out:
postgres@db2:~$ psql
psql (10.15 (Ubuntu 10.15-0ubuntu0.18.04.1))
Type "help" for help.
postgres=# create database foo;
ERROR: cannot execute CREATE DATABASE in a read-only transaction
Ahhh, we have a read-only replica, and that’s exactly what we want.
Now, let’s do the same statement on the primary and make sure it propagates:
postgres=# create database foo;
CREATE DATABASE
Cool, now, check the follower:
postgres=# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+---------+---------+-----------------------
foo | postgres | UTF8 | C.UTF-8 | C.UTF-8 |
postgres | postgres | UTF8 | C.UTF-8 | C.UTF-8 |
template0 | postgres | UTF8 | C.UTF-8 | C.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | C.UTF-8 | C.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(4 rows)
Cool, the foo
database is automatically replicated to our follower.
Lastly, we need to add our second follower to the cluster.
The steps will be the same as we’ve done for our first follower (don’t forget to create a new replication slot).
postgres=# select * from pg_create_physical_replication_slot('db3');
slot_name | lsn
-----------+-----
db3 |
(1 row)
postgres@db3:~$ pg_lsclusters
Ver Cluster Port Status Owner Data directory Log file
10 main 5432 online postgres /var/lib/postgresql/10/main /var/log/postgresql/postgresql-10-main.log
postgres@db3:~$ pg_ctlcluster 10 main stop
Warning: stopping the cluster using pg_ctlcluster will mark the systemd unit as failed. Consider using systemctl:
sudo systemctl stop postgresql@10-main
postgres@db3:~$ rm -fr /var/lib/postgresql/10/main/*
pg_basebackup: removing contents of data directory "/var/lib/postgresql/10/main"
postgres@db3:~$ pg_basebackup -h 167.172.155.245 -p 5432 -U replication -D /var/lib/postgresql/10/main --progress -W --slot db3 --wal-method=stream --write-recovery-conf
Password:
31291/31291 kB (100%), 1/1 tablespace
postgres@db3:~$ grep ^hot_standby /etc/postgresql/10/main/postgresql.conf
hot_standby = on
postgres@db3:~$ pg_ctlcluster 10 main start
Warning: the cluster will not be running as a systemd service. Consider using systemctl:
sudo systemctl start postgresql@10-main
postgres@db3:~$ pg_lsclusters
Ver Cluster Port Status Owner Data directory Log file
10 main 5432 online,recovery postgres /var/lib/postgresql/10/main /var/log/postgresql/postgresql-10-main.log
postgres@db3:~$ psql
psql (10.15 (Ubuntu 10.15-0ubuntu0.18.04.1))
Type "help" for help.
postgres=# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+---------+---------+-----------------------
foo | postgres | UTF8 | C.UTF-8 | C.UTF-8 |
postgres | postgres | UTF8 | C.UTF-8 | C.UTF-8 |
template0 | postgres | UTF8 | C.UTF-8 | C.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | C.UTF-8 | C.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(4 rows)
All done!
On the primary, we can verify things like this (leave of the fields for more good stuff):
postgres=# select pid,usename,application_name,client_addr,state,sync_state from pg_stat_replication ;
pid | usename | application_name | client_addr | state | sync_state
-------+-------------+------------------+---------------+-----------+------------
16422 | replication | walreceiver | 161.35.13.16 | streaming | async
16565 | replication | walreceiver | 161.35.62.178 | streaming | async
(2 rows)
Lastly, if you’d like to promote one of the followers, you could do:
postgres@db3:~$ pg_ctlcluster 10 main promote
postgres@db3:~$ pg_lsclusters
Ver Cluster Port Status Owner Data directory Log file
10 main 5432 online postgres /var/lib/postgresql/10/main /var/log/postgresql/postgresql-10-main.log
Notice that it no long says recovery
in the status column.
Your primary and this follower have now effectively diverged.
Hopefully, in the next article, I’ll show you an easy way of doing failover/back.