Mike Slinn

Upgrading PostgreSQL Ubuntu

Published 2022-05-28. Last modified 2023-05-01.
Time to read: 3 minutes.

This page is part of the posts collection, categorized under PostgreSQL, Ubuntu.

This article describes my recommended steps to follow when upgrading a Postgres database forced upon you when upgrading Ubuntu, and the new OS includes a new release of Postgres.

I use PostgreSQL as the database for ScalaCourses.com. The site is served from an Ubuntu instance, and there is a backup instance.

When upgrading to Ubuntu 22.04, PostgreSQL upgraded from v13 to v14; I used pg_upgrade, which required temporarily swapping the ports that Postgres used.

However, I used pg_upgradecluster when upgrading to Ubuntu 23.04; and PostgreSQL upgraded from v14 to v15. Unfortunately, the documented procedure to use pg_upgradecluster has no consideration for systemd, and this makes the upgrade slightly more awkward than it might otherwise be.

While both approaches have issues, I found that it is easier and faster to upgrade a Postgres installation using pg_upgradecluster than using pg_upgrade.

Upgrade Backup System First

One of the benefits of a backup system is that you can work through upgrade problems on the backup, before doing the same with the live instance.

This article suggests to upgrade the entire system to Ubuntu 23.04, then upgrade the active Postgres database cluster. The alternative would be to add a new PPA for Postgres, and just update that program before upgrading the entire system; this merely substitutes one set of potential issues for another. The article also demonstrates renaming the extra database cluster that the Postgres upgrade creates, but that just creates cruft since it is not required for anything.

I decided to delete the new database cluster instead, since upgrading the old database cluster is all that is required. As you will read in the Upgrade Procedure section, this is also the advice given during the upgrade procedure.

I decided to first upgrade the Ubuntu OS, then migrate the Postgres database cluster.

Upgrade Removed NVidia Support

After running do-release upgrade on the production system, the system worked fine, except the console had no graphics. I fixed it by automatically installing all video drivers:

Shell
$ sudo ubuntu-drivers autoinstall

Examining the Postgres Databases

pg_lsclusters displays the available PostgreSQL database clusters. Prior to upgrading to Postgres 15, I had never deleted the old versions. They had quietly accumulated over the 11 years that I had been running ScalaCourses:

Shell
$ pg_lsclusters
Ver Cluster Port Status Owner    Data directory               Log file
9.5 main    5431 online postgres /var/lib/postgresql/9.5/main /var/log/postgresql/postgresql-9.5-main.log
9.6 main    5433 online postgres /var/lib/postgresql/9.6/main /var/log/postgresql/postgresql-9.6-main.log
10  main    5434 online postgres /var/lib/postgresql/10/main  /var/log/postgresql/postgresql-10-main.log
12  main    5432 online postgres /var/lib/postgresql/12/main  /var/log/postgresql/postgresql-12-main.log
13  main    5435 online postgres /var/lib/postgresql/13/main  /var/log/postgresql/postgresql-13-main.log
14  main    5436 online postgres /var/lib/postgresql/14/main  /var/log/postgresql/postgresql-14-main.log 

Listing the databases in the active cluster showed an unnecessary database:

Shell
$ sudo -iu postgres psql -l
List of databases
     Name     |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges
--------------+----------+----------+-------------+-------------+-----------------------
 bix_dev      | postgres | UTF8     | en_CA.UTF-8 | en_CA.UTF-8 |
 postgres     | postgres | UTF8     | en_CA.UTF-8 | en_CA.UTF-8 |
 scalacourses | postgres | UTF8     | en_CA.UTF-8 | en_CA.UTF-8 |
 template0    | postgres | UTF8     | en_CA.UTF-8 | en_CA.UTF-8 | =c/postgres          +
              |          |          |             |             | postgres=CTc/postgres
 template1    | postgres | UTF8     | en_CA.UTF-8 | en_CA.UTF-8 | =c/postgres          +
              |          |          |             |             | postgres=CTc/postgres
(5 rows) 

I do not care about the bix_dev database, so I decided to delete it. The database that I care about is called scalacourses.

Shell
$ sudo -iu postgres psql -c 'drop database bix_dev;'
psql (14.7 (Ubuntu 14.7-0ubuntu0.22.10.1))
Type "help" for help.

DROP DATABASE 

Upgrade Procedure

Ubuntu is normally upgraded by running do-release-upgrade. Near the end of that process, the following message appeared.

At this point postgresql-15 and postgresql-client-15 had just been installed. The message did not reflect the true state of the system, which caused me some consternation for a moment.

The PostgreSQL version 14 is obsolete, but the server or client packages are still installed. Please install the latest packages (postgresql-15 and postgresql-client-15) and upgrade the existing clusters with pg_upgradecluster (see manpage).

Please be aware that the installation of postgresql-15 will automatically create a default cluster 15/main. If you want to upgrade the 14/main cluster, you need to remove the already existing 15 cluster (pg_dropcluster --stop 15 main, see manpage for details).

The old server and client packages are no longer supported. After the existing clusters are upgraded, the postgresql-14 and postgresql-client-14 packages should be removed.

Please see /usr/share/doc/postgresql-common/README.Debian.gz for details.

Following is /usr/share/doc/postgresql-common/README.Debian.gz, which was referenced by the above message. Most of that file is uninteresting, however two sections are important, and we will look at them.

/usr/share/doc/postgresql-common/README.Debian.gz
PostgreSQL for Debian
=====================
PostgreSQL is a fully featured object-relational database management system. It supports a large part of the SQL standard and is designed to be extensible by users in many aspects. Its features include ACID transactions, foreign keys, views, sequences, subqueries, triggers, outer joins, multiversion concurrency control, and user-defined types and functions.
Since the on-disk data format of all major PostgreSQL versions (like 9.6, 11, etc.) is incompatible to each other, Debian's PostgreSQL packaging architecture is designed to maintain clusters of different major versions in parallel.
This postgresql-common package provides the common infrastructure and all frontend programs that users and administrators use. The version specific server and client programs are shipped in postgresql-*-<version> packages.
For a detailed description of the architecture, please see
/usr/share/doc/postgresql-common/README.md.gz
First steps for the impatient ----------------------------- Eventually you will not get around reading at least some parts of the manual, but if you want to get straight into playing SQL, here are the steps to create a database user and a database for the Unix user 'joe':
1. Install a database server with the major version of your choice ('postgresql-XY', e. g. 'postgresql-11'). Preferably the latest version, which you can get by installing the metapackage 'postgresql'. This will automatically create a default cluster 'main' with the database superuser 'postgres'.
2. Get a shell for the database superuser 'postgres'. If your system has an active root user, use su:
# su -s /bin/bash postgres
If your system uses sudo to get administrative rights, use sudo instead:
joe$ sudo -u postgres bash
3. In this postgres shell, create a database user with the same name as your Unix login:
$ createuser -DRS joe
For details about the options, see createuser(1).
4. Create a database "joework" which is owned by "joe":
$ createdb -O joe joework
For details about the options, see createdb(1).
5. Exit the postgres shell.
6. As user joe, you should now be able to connect to your database with
$ psql joework
Cluster management ------------------ For managing clusters, the following commands are provided (each with its own manual page):
pg_createcluster - Create a new cluster or integrate an existing one into the postgresql-common architecture. pg_dropcluster - Completely remove a cluster. pg_ctlcluster - Control the server process of a cluster (start, stop, restart). pg_lsclusters - Show a list of all existing clusters and their status. pg_upgradecluster - Migrate a cluster from one major version to another one. pg_renamecluster - Rename a cluster.
Please note that you can of course also use the upstream tools for creating clusters, such as initdb(1). However, please note that in this case you cannot expect *any* of above pg_* tools to work, since they use different configuration settings (SSL, data directories, etc.) and file locations (e. g. /etc/postgresql/11/main/postgresql.conf). If in doubt, then do *not* use initdb, but only pg_createcluster. Since merely installing postgresql-NN will already set up a default cluster which is ready to work, most people do not need to bother about initdb or pg_createcluster at all.
Port assignment --------------- Please note that the pg_* tools automatically manage the server ports unless you specify them manually. The first cluster which is ever created (by any major version) will run on the default port 5432, and each new cluster will use the next higher free one.
E. g. if you first install "postgresql-11" on a clean system, the default 11/main cluster will run on port 5432. If you then create another 11 cluster, or install the "postgresql-12" package, that new one will run on 5433.
Please use "pg_lsclusters" for displaying the cluster <-> port mapping, and please have a look at the pg_createcluster manpage (the --port option) for details.
Default clusters and upgrading ------------------------------ When installing a postgresql-NN package from scratch, a default cluster 'main' will automatically be created. This operation is equivalent to doing 'pg_createcluster NN main --start'.
Due to this default cluster, an immediate attempt to upgrade an earlier 'main' cluster to a new version will fail and you need to remove the newer default cluster first. E. g., if you have postgresql-9.6 installed and want to upgrade to 11, you first install postgresql-11:
apt-get install postgresql-11
Then drop the default 11 cluster that was just created:
pg_dropcluster 11 main --stop
And then upgrade the 9.6 cluster to the latest installed version (e. g. 11):
pg_upgradecluster 9.6 main
SSL --- The PostgreSQL server packages support SSL, which provides encrypted and authenticated network communication. SSL should be used if you have an untrusted network between a database server and a client and these exchange security sensitive data like passwords or confidential database contents.
When a cluster is created with pg_createcluster, SSL support will automatically be enabled. postgresql-common makes use of the 'snakeoil' SSL certificate that is generated by the ssl-cert package, so that SSL works out of the box (ssl_cert_file, ssl_key_file). In addition, if /etc/postgresql-common/root.crt exists, it will be used as CA certificate file (ssl_ca_file).
/etc/postgresql-common/root.crt is a dummy file by default, so that client-side authentication is not performed. To enable it, you should add some root certificates to it. A reasonable choice is to just symlink the file to /etc/ssl/certs/ssl-cert-snakeoil.pem; in this case, client certificates need to be signed by the snakeoil certificate, which might be desirable in many cases. See
/usr/share/doc/postgresql-doc-11/html/ssl-tcp.html
for details (in package postgresql-doc).
Further documentation --------------------- All commands shipped by postgresql-common have detailed manpages. See postgresql-common(7) for the documentation of the database client program wrapping, and user_clusters(5) and postgresqlrc(5) for the cluster configuration.
The documentation of the database server and client functions, SQL commands, modules, etc. documented is shipped in the per-version packages postgresql-doc-<version>.

The Cluster management section in the above file provides some background information for our purposes, so I repeat it here:

Cluster management
For managing clusters, the following commands are provided (each with
its own manual page):

pg_createcluster  - Create a new cluster or integrate an existing one
                    into the postgresql-common architecture.
pg_dropcluster    - Completely remove a cluster.
pg_ctlcluster     - Control the server process of a cluster
                    (start, stop, restart).
pg_lsclusters     - Show a list of all existing clusters and their status.
pg_upgradecluster - Migrate a cluster from one major version to another one.
pg_renamecluster  - Rename a cluster.

Please note that you can of course also use the upstream tools for
creating clusters, such as initdb(1). However, please note that in
this case you cannot expect *any* of above pg_* tools to work, since
they use different configuration settings (SSL, data directories,
etc.) and file locations (e. g.
/etc/postgresql/11/main/postgresql.conf). If in doubt, then do *not*
use initdb, but only pg_createcluster. Since merely installing
postgresql-NN will already set up a default cluster which is ready to
work, most people do not need to bother about initdb or
pg_createcluster at all.

The Default clusters and upgrading section is more important, however it contained errors. I fixed the errors in the following instructions.

Default clusters and upgrading
When installing a postgresql-NN package from scratch, a default
cluster 'main' will automatically be created. This operation is
equivalent to doing 'pg_createcluster NN main --start'.

Due to this default cluster, an immediate attempt to upgrade an
earlier 'main' cluster to a new version will fail and you need to
remove the newer default cluster first. E. g., if you have
postgresql-9.6 installed and want to upgrade to 11, you first install
postgresql-11:

yes | sudo apt install postgresql-11

Then drop the default 11 cluster that was just created:

sudo -iu postgres pg_dropcluster 11 main --stop

And then upgrade the 9.6 cluster to the latest installed version (e. g. 11):

sudo -iu postgres pg_upgradecluster 9.6 main

Systemd can significantly affect the upgrade process, and unfortunately it is completely ignored by the above instructions. I muddled through, as you will see.

Transcript

The following is a transcript of what I actually did:

Shell
$ sudo -iu postgres pg_dropcluster --stop 15 main
Warning: stopping the cluster using pg_ctlcluster will mark the systemd
unit as failed. Consider using systemctl:
  sudo systemctl stop postgresql@15-main
Warning: systemd was not informed about the removed cluster yet. Operations like
"service postgresql start" might fail. To fix, run:
  sudo systemctl daemon-reload 

$ sudo -iu postgres pg_upgradecluster 14 main
Stopping old cluster...
Warning: stopping the cluster using pg_ctlcluster will mark the systemd unit as failed.
Consider using systemctl:
  sudo systemctl stop postgresql@14-main
Restarting old cluster with restricted connections...
Notice: extra pg_ctl/postgres options given, bypassing systemctl for start operation
Creating new PostgreSQL cluster 15/main ...
/usr/lib/postgresql/15/bin/initdb -D /var/lib/postgresql/15/main --auth-local peer --auth-host scram-sha-256 --no-instructions --encoding UTF8 --lc-collate en_CA.UTF-8 --lc-ctype en_CA.UTF-8
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale "en_CA.UTF-8".
The default text search configuration will be set to "english".

Data page checksums are disabled.

fixing permissions on existing directory /var/lib/postgresql/15/main ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... America/Toronto
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok
Warning: systemd does not know about the new cluster yet. Operations like
"service postgresql start" will not handle it. To fix, run:
  sudo systemctl daemon-reload

Copying old configuration files...
Copying old start.conf...
Copying old pg_ctl.conf...
Starting new cluster...
Notice: extra pg_ctl/postgres options given, bypassing systemctl for start operation
Roles, databases, schemas, ACLs...
 set_config
------------

(1 row)

set_config
------------

(1 row)

set_config
------------

(1 row)

set_config
------------

(1 row)

Fixing hardcoded library paths for stored procedures...
Upgrading database template1...
Analyzing database template1...
Fixing hardcoded library paths for stored procedures...
Upgrading database postgres...
Analyzing database postgres...
Fixing hardcoded library paths for stored procedures...
Upgrading database scalacourses...
Analyzing database scalacourses...
Stopping target cluster...
Stopping old cluster...
Disabling automatic startup of old cluster...
Starting upgraded cluster on port 5432...
Warning: the cluster will not be running as a systemd service.
Consider using systemctl:
  sudo systemctl start postgresql@15-main

Success. Please check that the upgraded cluster works. If it does,
you can remove the old cluster with
     pg_dropcluster 14 main

Ver Cluster Port Status Owner    Data directory              Log file
14  main    5433 down   postgres /var/lib/postgresql/14/main /var/log/postgresql/postgresql-14-main.log
Ver Cluster Port Status Owner    Data directory              Log file
15  main    5432 online postgres /var/lib/postgresql/15/main /var/log/postgresql/postgresql-15-main.log 

When I attempted to following the above instructions for systemd, errors appeared:

Shell
$ sudo systemctl daemon-reload

$ sudo systemctl start postgresql@15-main
Job for postgresql@15-main.service failed because the service did not take the steps required by its unit configuration.
See "systemctl status postgresql@15-main.service" and "journalctl -xeu postgresql@15-main.service" for details. 

Great, an obscure failure message. Others have fussed with this problem. I expected that rebooting would be much faster than trying to fix it, and that did prove to be the case:

Shell
$ sudo reboot

After rebooting, the upgraded Postgres cluster worked fine:

Shell
$ pg_lsclusters
Ver Cluster Port Status Owner    Data directory              Log file
14  main    5433 down   postgres /var/lib/postgresql/14/main /var/log/postgresql/postgresql-14-main.log
15  main    5432 online postgres /var/lib/postgresql/15/main /var/log/postgresql/postgresql-15-main.log

$ sudo -iu postgres psql -l
List of databases
    Name     |    Owner     | Encoding |   Collate   |    Ctype    | ICU Locale | Locale Provider |   Access privileges
--------------+--------------+----------+-------------+-------------+------------+-----------------+-----------------------
postgres     | postgres     | UTF8     | en_CA.UTF-8 | en_CA.UTF-8 |            | libc            |
scalacourses | scalacourses | UTF8     | en_CA.UTF-8 | en_CA.UTF-8 |            | libc            |
template0    | postgres     | UTF8     | en_CA.UTF-8 | en_CA.UTF-8 |            | libc            | =c/postgres          +
              |              |          |             |             |            |                 | postgres=CTc/postgres
template1    | postgres     | UTF8     | en_CA.UTF-8 | en_CA.UTF-8 |            | libc            | =c/postgres          +
              |              |          |             |             |            |                 | postgres=CTc/postgres
 (4 rows) 

Here is how I deleted the older database clusters:

Shell
$ for X in 9.5 9.6 10 12 13; do
  sudo -iu postgres pg_dropcluster --stop $X main
end
😁

All done!

* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.