POSTGRESQL MAJOR UPGRADE METHODS

There are 3 different methods to perform major version upgrades on your PostgreSQL databases. These are:

  • Moving all data to a database cluster created in the target version with pg_dumpall
  • Refreshing system tables and using user data as is with pg_upgrade
  • Moving all data to the new version in a standby environment with logical replication and changing roles with switch-over.

Let’s quickly go over the advantages/disadvantages of these methods. Since upgrading with pg_dumpall means copying all the data, its applicability decreases as the database size increases. As the size increases, the downtime and the possibility of errors in data transfer will increase. In addition, the disk space we will need for the upgrade process may become a problem. As an advantage, we can mention that the process is safe since the cluster data in the old version is not overwritten, and that all objects in the databases are recreated, fixing problems such as fragmentation and “index bloat”. We also have the option to speed up our upgrade process by performing data export and import operations with a single command as in the example below.

$ pg_dumpall -p 5432 | psql -d postgres -p 5433

You can review the upgrade steps of this method, from the PostgreSQL documentation:

pg_upgrade is a method that will make our work easier in large sizes. With the “–link” option offered by pg_upgrade, existing data files can be used by creating a “hard link” on them without having to move the data. (By the way, if it is run with the default settings, pg_upgrade copies the database files from the data directory in the old version to the data directory in the new version.) In the link method, the old version cluster cannot be opened again properly after the upgrade process, so it is important to back up the databases before the upgrade. Another important point is that in order to be able to upgrade in link mode, the data directories of the old and new clusters must be on the same filesystem. It is also possible to perform checks before the upgrade process with the “–check” option of pg_upgrade. Again, you can find more detailed information in the official PostgreSQL documentation.

The third method is an option that we can choose to minimize the upgrade time of large databases by using the advantage of the “logical replication” configuration to work in different versions (primary/standby). After installing our standby database on the same server as the primary or on a different server and the target PostgreSQL version and completing the replication configuration, we can complete the upgrade process in a short time by performing the switch-over process while the databases are synchronized. For details

After examining these three methods within the scope of PostgreSQL major upgrade, we will also talk about “pg_upgradecluster”, which makes our job easier in Ubuntu/Debian environments. This program, which comes with the “postgresql-common” package, performs the PostgreSQL cluster upgrade to the new version with a single command, while the configuration files of the old version are copied by adjusting them in accordance with the new version cluster. When using “pg_upgradecluster”, we can choose pg_dump or pg_upgrade for the upgrade method.

-m, –method=dump|upgrade|link|clone

  • “dump” option uses pg_dump and pg_restore, while the “upgrade” option uses pg_upgrade, the default is “dump”.
  • “link” option is a shortcut for “upgrade –link” and uses pg_upgrade with the link option.
  • “clone” is a shortcut for “upgrade –clone” and uses pg_upgrade with the clone option.

The “pg_upgrade –clone” option, which we did not mention before, is an option that is supported only by certain filesystems and during the upgrade, it creates a clone of the files in the old cluster in the new cluster as quickly as in the hard link method, but unlike the hard link, it keeps the original blocks of the old files and makes the old cluster openable. This method, also known as “reflink”, is actually a filesystem feature that allows two different files to use common data blocks.

You can find more information and examine the command options in the man page of pg_upgradecluster.

Now let’s go over the steps of upgrading our PostgreSQL 16 version cluster to version 17 with pg_upgradecluster. In our example, there are two version 16 clusters running and we will upgrade one of them to version 17.

# pg_lsclusters
Ver Cluster Port Status Owner    Data directory              Log file
16  main    5432 online postgres /var/lib/postgresql/16/main /var/log/postgresql/postgresql-16-main.log
16  test    5433 online postgres /var/lib/postgresql/16/test /var/log/postgresql/postgresql-16-test.log

First, we install the new version of PostgreSQL on our server.

# sudo apt-get upgrade
# wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
# sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" >> /etc/apt/sources.list.d/pgdg.list'
# sudo apt-get  install postgresql-17 postgresql-client-17

With the installation of the new version, a new cluster named “main” was automatically started. We are deleting this cluster since we will not be using it.

# pg_lsclusters
Ver Cluster Port Status Owner    Data directory              Log file
16  main    5432 online postgres /var/lib/postgresql/16/main /var/log/postgresql/postgresql-16-main.log
16  test    5433 online postgres /var/lib/postgresql/16/test /var/log/postgresql/postgresql-16-test.log
17  main    5434 online postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log

# pg_dropcluster --stop 17 main

# pg_lsclusters
Ver Cluster Port Status Owner    Data directory              Log file
16  main    5432 online postgres /var/lib/postgresql/16/main /var/log/postgresql/postgresql-16-main.log
16  test    5433 online postgres /var/lib/postgresql/16/test /var/log/postgresql/postgresql-16-test.log

Now we update the cluster named “test” to version 17 using the pg_upgradecluster command with its default settings. This will create a cluster named “test” in version 17 and move the data to the new cluster using pg_dump. It will also move the configuration files and change the port number of the old cluster in version 16.

# pg_upgradecluster 16 test

	Stopping old cluster...
	Restarting old cluster with restricted connections...
	Notice: extra pg_ctl/postgres options given, bypassing systemctl for start operation
	Creating new PostgreSQL cluster 17/test ...
	/usr/lib/postgresql/17/bin/initdb -D /var/lib/postgresql/17/test --auth-local peer --auth-host scram-sha-256 --no-instructions --encoding UTF8 --lc-collate en_US.UTF-8 --lc-ctype en_US.UTF-8 --locale-provider libc
	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_US.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/17/test ... ok
	creating subdirectories ... ok
	selecting dynamic shared memory implementation ... posix
	selecting default "max_connections" ... 100
	selecting default "shared_buffers" ... 128MB
	selecting default time zone ... Etc/UTC
	creating configuration files ... ok
	running bootstrap script ... ok
	performing post-bootstrap initialization ... ok
	syncing data to disk ... ok

	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
	Running init phase upgrade hook scripts ...

	Roles, databases, schemas, ACLs...
	 set_config
	------------

	(1 row)

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

	(1 row)

	Fixing hardcoded library paths for stored procedures...
	Upgrading database postgres...
	Fixing hardcoded library paths for stored procedures...
	Upgrading database template1...
	Stopping target cluster...
	Stopping old cluster...
	Disabling automatic startup of old cluster...
	Starting upgraded cluster on port 5433...
	Running finish phase upgrade hook scripts ...
	vacuumdb: processing database "postgres": Generating minimal optimizer statistics (1 target)
	vacuumdb: processing database "template1": Generating minimal optimizer statistics (1 target)
	vacuumdb: processing database "postgres": Generating medium optimizer statistics (10 targets)
	vacuumdb: processing database "template1": Generating medium optimizer statistics (10 targets)
	vacuumdb: processing database "postgres": Generating default (full) optimizer statistics
	vacuumdb: processing database "template1": Generating default (full) optimizer statistics

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

	Ver Cluster Port Status Owner    Data directory              Log file
	16  test    5434 down   postgres /var/lib/postgresql/16/test /var/log/postgresql/postgresql-16-test.log
	Ver Cluster Port Status Owner    Data directory              Log file
	17  test    5433 online postgres /var/lib/postgresql/17/test /var/log/postgresql/postgresql-17-test.log


# pg_lsclusters
Ver Cluster Port Status Owner    Data directory              Log file
16  main    5432 online postgres /var/lib/postgresql/16/main /var/log/postgresql/postgresql-16-main.log
16  test    5434 down   postgres /var/lib/postgresql/16/test /var/log/postgresql/postgresql-16-test.log
17  test    5433 online postgres /var/lib/postgresql/17/test /var/log/postgresql/postgresql-17-test.log

Now, let’s use the “pg_upgrade –link” method when running pg_upgradecluster. Let’s delete the cluster we upgraded, reopen our old test cluster, and then run the pg_upgradecluster command with the “link” method and try the “parallel” and “rename” options.

# pg_dropcluster 17 test --stop
# pg_ctlcluster start 16 test
# pg_lsclusters
Ver Cluster Port Status Owner    Data directory              Log file
16  main    5432 online postgres /var/lib/postgresql/16/main /var/log/postgresql/postgresql-16-main.log
16  test    5434 online postgres /var/lib/postgresql/16/test /var/log/postgresql/postgresql-16-test.log


# pg_upgradecluster 16 test --method link --jobs 2 --rename test17

	Stopping old cluster...
	Creating new PostgreSQL cluster 17/test17 ...
	/usr/lib/postgresql/17/bin/initdb -D /var/lib/postgresql/17/test17 --auth-local peer --auth-host scram-sha-256 --no-instructions --encoding UTF8 --lc-collate en_US.UTF-8 --lc-ctype en_US.UTF-8 --locale-provider libc
	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_US.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/17/test17 ... ok
	creating subdirectories ... ok
	selecting dynamic shared memory implementation ... posix
	selecting default "max_connections" ... 100
	selecting default "shared_buffers" ... 128MB
	selecting default time zone ... Etc/UTC
	creating configuration files ... ok
	running bootstrap script ... ok
	performing post-bootstrap initialization ... ok
	syncing data to disk ... ok

	Copying old configuration files...
	Copying old start.conf...
	Copying old pg_ctl.conf...
	Running init phase upgrade hook scripts ...

	/usr/lib/postgresql/17/bin/pg_upgrade -b /usr/lib/postgresql/16/bin -B /usr/lib/postgresql/17/bin -p 5434 -P 5433 -d /etc/postgresql/16/test -D /etc/postgresql/17/test17 --link -j 2
	Finding the real data directory for the source cluster        ok
	Finding the real data directory for the target cluster        ok
	Performing Consistency Checks
	-----------------------------
	Checking cluster versions                                     ok
	Checking database user is the install user                    ok
	Checking database connection settings                         ok
	Checking for prepared transactions                            ok
	Checking for contrib/isn with bigint-passing mismatch         ok
	Checking data type usage                                      ok
	Creating dump of global objects                               ok
	Creating dump of database schemas
																  ok
	Checking for presence of required libraries                   ok
	Checking database user is the install user                    ok
	Checking for prepared transactions                            ok
	Checking for new cluster tablespace directories               ok

	If pg_upgrade fails after this point, you must re-initdb the
	new cluster before continuing.

	Performing Upgrade
	------------------
	Setting locale and encoding for new cluster                   ok
	Analyzing all rows in the new cluster                         ok
	Freezing all rows in the new cluster                          ok
	Deleting files from new pg_xact                               ok
	Copying old pg_xact to new server                             ok
	Setting oldest XID for new cluster                            ok
	Setting next transaction ID and epoch for new cluster         ok
	Deleting files from new pg_multixact/offsets                  ok
	Copying old pg_multixact/offsets to new server                ok
	Deleting files from new pg_multixact/members                  ok
	Copying old pg_multixact/members to new server                ok
	Setting next multixact ID and offset for new cluster          ok
	Resetting WAL archives                                        ok
	Setting frozenxid and minmxid counters in new cluster         ok
	Restoring global objects in the new cluster                   ok
	Restoring database schemas in the new cluster
																  ok
	Adding ".old" suffix to old global/pg_control                 ok

	If you want to start the old cluster, you will need to remove
	the ".old" suffix from /var/lib/postgresql/16/test/global/pg_control.old.
	Because "link" mode was used, the old cluster cannot be safely
	started once the new cluster has been started.
	Linking user relation files
																  ok
	Setting next OID for new cluster                              ok
	Sync data directory to disk                                   ok
	Creating script to delete old cluster                         ok
	Checking for extension updates                                ok

	Upgrade Complete
	----------------
	Optimizer statistics are not transferred by pg_upgrade.
	Once you start the new server, consider running:
		/usr/lib/postgresql/17/bin/vacuumdb --all --analyze-in-stages
	Running this script will delete the old cluster's data files:
		./delete_old_cluster.sh
	pg_upgradecluster: pg_upgrade output scripts are in /var/log/postgresql/pg_upgradecluster-16-17-test17.WyLf
	Disabling automatic startup of old cluster...
	Starting upgraded cluster on port 5434...
	Running finish phase upgrade hook scripts ...
	vacuumdb: processing database "postgres": Generating minimal optimizer statistics (1 target)
	vacuumdb: processing database "template1": Generating minimal optimizer statistics (1 target)
	vacuumdb: processing database "postgres": Generating medium optimizer statistics (10 targets)
	vacuumdb: processing database "template1": Generating medium optimizer statistics (10 targets)
	vacuumdb: processing database "postgres": Generating default (full) optimizer statistics
	vacuumdb: processing database "template1": Generating default (full) optimizer statistics

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

	Ver Cluster Port Status Owner    Data directory              Log file
	16  test    5433 down   postgres /var/lib/postgresql/16/test /var/log/postgresql/postgresql-16-test.log
	Ver Cluster Port Status Owner    Data directory                Log file
	17  test17  5434 online postgres /var/lib/postgresql/17/test17 /var/log/postgresql/postgresql-17-test17.log



# pg_lsclusters
Ver Cluster Port Status Owner    Data directory                Log file
16  main    5432 online postgres /var/lib/postgresql/16/main   /var/log/postgresql/postgresql-16-main.log
16  test    5433 down   postgres /var/lib/postgresql/16/test   /var/log/postgresql/postgresql-16-test.log
17  test17  5434 online postgres /var/lib/postgresql/17/test17 /var/log/postgresql/postgresql-17-test17.log

You can also see the pre and post operations performed by pg_upgradecluster along with the upgrade operation in the command output. Check out this warning in the output : Because “link” mode was used, the old cluster cannot be started safely once the new cluster has been started.

Regardless of the method, the two most important points in upgrade processes are correct preparation and creating a fallback plan. In the correct preparation stage, it is very important to test all applications running on the database on the target version of the database, and also to test and document the upgrade steps. The fallback plan is a preparation against serious problems that may occur during or after the upgrade process. In cases where the database cannot be transferred to the new version properly or the application functions do not work correctly in the new version and there is no chance of fixing it in a short time, it is necessary to be able to switch to the old version without losing data. For this, we should create and test our fallback plan before the upgrade.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir