Simple guide for migrating PostgreSQL cluster to new major version using 2 instances (current one and new one), logical replication and pgbouncer as intermediate connection pooler able to forward connections to new instance with no noticeable interruption.
Fill in vars
file (per https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS).
OLD_CONNECTION
= connection to current DBNEW_CONNECTION
= connection to new DBSUB_CONNECTION
= connection to current DB as used from new DB (usually same asOLD_CONNECTION
, but differs in docker based test example due to specific docker networking)PGB_CONNECTION
= connection to pgbouncer
$ cp vars.example vars
# update vars
$ source vars
pgbouncer
is temporarily needed for migration to hand over opened connections. Initially, it needs to be pointing to old DB. For simple setup on Debain system, it is enough to reuse example config from this repository. If using custom pgbouncer
setup, please tweak switch.sh
according to your needs. Example setup could use included pgbouncer.yaml
to deploy "utility pod" and service into K8s cluster.
psql
and pgbouncer
programs needs to be installed for migration scripts to work.
apt-get update
apt-get install -y postgresql-client pgbouncer vim git procps pspg
export PAGER=pspg
git clone https://github.com/rubygems/pg-major-update.git
cd pg-major-update
vim vars # update vars
source vars
This is super simple example of running pgbouncer totally unsecured. It could be safe when running internally and not being exposed in public.
useradd pgbouncer
mkdir /tmp/pgb
chown -R pgbouncer:pgbouncer /tmp/pgb
cp configs/pgbouncer.ini pgbouncer.ini
echo "db = $OLD_CONNECTION" >> pgbouncer.ini
pgbouncer -d pgbouncer.ini --user pgbouncer
./scripts/checks.sh
./scripts/replicate.sh
Await until initial replication sync is done. Peek at pg_stat_subscription
on new DB or pg_replication_slots
at old DB could be handy .
psql "$NEW_CONNECTION" -c "SELECT * FROM pg_stat_subscription" # only one line with migration_sub should be present
psql "$OLD_CONNECTION" -c "SELECT * FROM pg_replication_slots" # only one line with migration_sub should be present
This depends on application configuration. For example it could be enough to change DATABASE_URL
secret in K8s and restart deployment to pickup new value.
Notice pgbouncer doesn't support all features like prepared statements and LISTEN. See https://www.pgbouncer.org/features.html#sql-feature-map-for-pooling-modes for more info. For Rails application it could be enough to configure ActiveRecord to not use prepared statements temporarily for time of the upgrade. Other processes relying on those features like GoodJob could be temporarily stopped during the migration.
./scripts/pause.sh && ./scripts/lag.sh && ./scripts/sequences.sh && ./scripts/unreplicate.sh && ./scripts/switch.sh && ./scripts/resume.sh
Again, this depends on application configuration. For example it could be enough to change DATABSAE_URL
secret in K8s and restart deployment to pickup new value. Since app doesn't need pgbouncer
anymore, services like GoodJob can be started and pgbouncer can be removed.
pgbouncer
, tar
and gunzip
needs to be installed locally. Two PostgreSQL instances for testing will be created using Docker (docker-compose).
cp vars.example vars # preconfigured values for docker-compose.yml and configs/pgbouncer.ini
docker compose up -d
./scripts/init.sh # start local pg instances and pgbouncer
./scripts/replicate.sh # start replication
# wait unless initial replication is in sync, for example with following command
psql "$NEW_CONNECTION" -c "SELECT * FROM pg_stat_subscription" # only one line should be present
# once ready
./scripts/pause.sh # pause old cluster and wait unless connections are gone
# wait unless replication is in sync
# this step can fail after timeout when replication is not in sync and automatically resumes pgbouncer connections
# it is ok to try later
./scripts/lag.sh
./scripts/sequences.sh # sync sequences manually
./scripts/unreplicate.sh # stop replication
./scripts/switch.sh # switch pgbouncer to new cluster
./scripts/resume.sh # resume
This leaves pgbouncer
running and pointing to new DB cluster. pgbouncer
can be stopped using kill $(cat /tmp/pgbouncer.pid)
or similar command. To stop PostgreSQL testing instances you can use docker compose stop
.