pgd node upgrade
================

Synopsis
--------

The ``pgd node upgrade`` command is used to upgrade the Postgres version
on a node in the EDB Postgres Distributed cluster.

Modes of operation
------------------

``pgd node upgrade`` runs in one of three modes, depending on whether
``--prepare`` or ``--check`` is set. ``--prepare`` and ``--check`` can’t
be used together.

.. csv-table::
  :header: Mode,Flag,Effect
  :widths: 12,15,15
  :align: left
  :class: longtable

  Prepare only,`--prepare`,"Runs `initdb` on the new data directory and migrates the old cluster's configuration into it, without running `pg_upgrade`. Idempotent, so it's safe to re-run, for example to change a configuration override."
  Dry run,`--check`,"Runs `pg_upgrade` in dry-run mode to validate that the upgrade would succeed, without performing it. Requires the new data directory to already be initialized, either manually (for example with `initdb`) or using `--prepare`."
  Full upgrade,Neither flag,"Runs the  :ref:`Safety checks before upgrading<Safety checks before upgrading>` , then `pg_upgrade` against the new data directory, followed by the BDR post-upgrade steps."

For a predictable, low-downtime upgrade, run all three in sequence:
prepare the new data directory ahead of the maintenance window, dry-run
the upgrade to confirm it will succeed, then perform the actual upgrade.
See :ref:`Preparing the new data directory ahead of the upgrade <Preparing the new data directory ahead of the upgrade>`  for a worked example.

..  Note::
   Starting from PGD 6.4, `pgd node setup`  advances the `NextOID`  counter before creating BDR objects in single-user mode, preventing those objects from receiving system-range OIDs (< 16384) that would cause upgrade failures. Before performing the upgrade, the command checks whether existing BDR objects were created with system-range OIDs. If detected, the command fails immediately with a clear error identifying the affected objects. See  :ref:`Resolving system-range OID errors <Resolving system-range OID errors>`  for steps to recover.

Users and roles
^^^^^^^^^^^^^^^

Requires Postgres superuser privileges and the ``bdr_superuser`` role.
See :ref:`User roles <User roles>`  .

Syntax
------

.. code:: plaintext

   pgd node <NODE_NAME> upgrade [OPTIONS] --old-bindir <OLD_BINDIR> --new-bindir <NEW_BINDIR> --old-datadir <OLD_DATADIR> --new-datadir <NEW_DATADIR> --database <DATABASE> --username <USER_NAME>

Where ``<NODE_NAME>`` is the name of the node which you want to upgrade
and ``<OLD_BINDIR>`` , ``<NEW_BINDIR>`` , ``<OLD_DATADIR>`` ,
``<NEW_DATADIR>`` , ``<DATABASE>`` , and ``<USER_NAME>`` are the old and
new Postgres instance bin directories, old and new Postgres instance
data directories, database name, and cluster’s install user name
respectively.

Options
-------

The following table lists the options available for the
``pgd node upgrade`` command:

.. csv-table::
  :header: Short,Long,Default,Env,Description
  :widths: 6,10,8,8,15
  :align: left
  :class: longtable

  -b,--old-bindir,"",PGBINOLD,Old Postgres instance bin directory
  -B,--new-bindir,"",PGBINNEW,New Postgres instance bin directory
  -d,--old-datadir,"",PGDATAOLD,Old Postgres instance data directory
  -D,--new-datadir,"",PGDATANEW,New Postgres instance data directory
  "",--database,"",PGDATABASE,PGD database name
  -p,--old-port,Read from the old cluster's `postgresql.conf`; falls back to 5432 only if that lookup fails,PGPORTOLD,Old Postgres instance port
  "",--socketdir,/var/run/postgresql,PGSOCKETDIR,"Directory to use for postmaster sockets during upgrade, for both the old and new instance"
  "",--check,"","","Run in dry-run mode, validating that the upgrade would succeed without performing it. Requires the new data directory to already be initialized, either manually (for example with `initdb`) or using `--prepare`. Can't be used with `--prepare`."
  "",--prepare,"","","Only perform pre-upgrade preparation (`initdb` and configuration migration) on the new data directory, without running the upgrade. Can't be used with `--check`."
  -j,--jobs,1,"",Number of simultaneous processes or threads to use
  -k,--link,"","",Use hard links instead of copying files to the new cluster
  "",--old-options,"","","Option to pass to old postgres command, multiple invocations are appended"
  "",--new-options,"","","Option to pass to new postgres command, multiple invocations are appended"
  -N,--no-sync,"","",Don't wait for all files in the upgraded cluster to be written to disk
  -P,--new-port,5432,PGPORTNEW,New Postgres instance port number
  -r,--retain,"","",Retain SQL and log files even after successful completion
  -U,--username,"",PGUSER,Cluster's install user name
  "",--clone,"","",Use efficient file cloning
  "",--copy-by-block,"","",Used to migrate data between clusters with different encryption settings. This option is supported for databases that use Transparent Data Encryption (TDE)
  -y,--data-encryption,"","",Enable Transparent Data Encryption (TDE) on the new data directory. Only takes effect with `--prepare`.
  "",--data-encryption-keylen,128,"","AES key length for TDE, either `128` or `256`. Requires `--data-encryption`."
  "",--key-wrap-command,"",PGDATAKEYWRAPCMD,Command to wrap (encrypt) the data encryption key. The command must contain the placeholder `%p`. Requires `--data-encryption`. Can't be used with `--no-key-wrap`.
  "",--key-unwrap-command,"",PGDATAKEYUNWRAPCMD,Command to unwrap (decrypt) the data encryption key and access the files to copy. The command must be the same specified during the server initialization using `pgd node setup`
  "",--no-key-wrap,"","",Store the data encryption key unwrapped (raw) on disk. Not recommended for production use. Requires `--data-encryption`.
  "",--copy-key-from,"","","Reuse an existing data encryption key file instead of generating a new one, preserving the source cluster's key material. Requires `--data-encryption`, paired with either `--key-wrap-command` or `--no-key-wrap` to describe how the reused key is encoded on disk."
  "",--postgresql-conf,"","",Path of the `postgresql.conf` file to use for the new node. Only takes effect with `--prepare`.
  "",--postgresql-auto-conf,"","",Path of the `postgresql.auto.conf` file to use for the new node. Only takes effect with `--prepare`.
  "",--hba-conf,"","",Path of the `pg_hba.conf` file to use for the new node. Only takes effect with `--prepare`.
  -v,--verbose,"","",Print extra diagnostic output during `--prepare` steps.

See also `Global Options <https://www.enterprisedb.com/docs/pgd/latest/reference/cli/command_ref/#global-options>`_  .

After a successful upgrade, the command prints a closing summary
confirming completion, along with next steps for starting the new
Postgres instance (see :ref:`pgd node start <pgd node start>`  ) and any remaining post-upgrade
tasks.

Safety checks before upgrading
------------------------------

Before invoking ``pg_upgrade`` , the regular upgrade and ``--check``
paths run several non-destructive checks:

- **Leader status.** If the node being upgraded is the current write
  leader or Raft leader of any group, the command rejects the upgrade
  and prints the switchover command to run first, for example
  :ref:`pgd group set-leader <pgd group set-leader>`  or :ref:`pgd group set-leader <pgd group set-leader>`  , for each group the node leads.
  There’s no flag to skip this check, since switching leadership away
  from the node being upgraded is a deliberate step, not something to
  automate around.

- **Old cluster reachability.** If the old cluster is reachable, the
  command uses it to detect encoding and configuration paths. If it’s
  not reachable, for example because it’s already stopped ahead of
  ``pg_upgrade`` , the command falls back to single-user mode against
  the old data directory instead of requiring the old postmaster to be
  running. In this case, the leader-status check is skipped rather than
  failing, since PGD has already re-elected a leader among the surviving
  nodes.

- **New data directory initialized.** If ``<new_datadir>`` is missing or
  empty, the command fails with a message pointing at ``--prepare``
  instead of a raw ``pg_upgrade`` error.

- **BDR extension and version compatibility.** The upgrade is rejected
  if the BDR extension isn’t installed in ``<DATABASE>`` on the old
  cluster, or if the old cluster’s BDR version is below the supported
  minimum or higher than the new cluster’s.

Resolving system-range OID errors
---------------------------------

If the command fails with a system-range OID error, the cluster was set
up with a version of ``pgd node setup`` earlier than 6.4 that didn’t
advance the ``NextOID`` counter before creating BDR objects in
single-user mode. The affected objects can’t be reassigned new OIDs in
place.

To resolve the error, part the node, drop and recreate the BDR extension
in a standard psql session (not single-user mode), then rejoin the
cluster without synchronizing structure to preserve existing data.

1. Part the node from the cluster:

.. code:: shell

      pgd node <node-name> part

2. In a psql session connected to the BDR database, drop the extension:

.. code:: sql

      DROP EXTENSION bdr CASCADE;

3. In a standard psql session (not single-user mode), recreate the
   extension:

.. code:: sql

      CREATE EXTENSION bdr;

4. Use :ref:`bdr.create_node() <pgd commit-scope create>`  and :ref:`bdr.join_node_group() <System functions>`  to recreate the node and rejoin
   the group, with ``synchronize_structure`` set to ``'none'`` .

5. Run ``pgd node upgrade`` again.

Examples
--------

In the following examples, “kaolin” is the name of the node to upgrade,
from the Quickstart democluster.

Upgrade the Postgres version on a node
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code:: shell

   pgd node kaolin upgrade --old-bindir /usr/pgsql-16/bin --new-bindir /usr/pgsql-17/bin --old-datadir /var/lib/pgsql/16/data --new-datadir /var/lib/pgsql/17/data --database pgddb --username enterprisedb

Upgrade the Postgres version on a node with hard links
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code:: shell

   pgd node kaolin upgrade --old-bindir /usr/pgsql-16/bin --new-bindir /usr/pgsql-17/bin --old-datadir /var/lib/pgsql/16/data --new-datadir /var/lib/pgsql/17/data --database pgddb --username enterprisedb --link

Upgrade the Postgres version on a node with efficient file cloning
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code:: shell

   pgd node kaolin upgrade --old-bindir /usr/pgsql-16/bin --new-bindir /usr/pgsql-17/bin --old-datadir /var/lib/pgsql/16/data --new-datadir /var/lib/pgsql/17/data --database pgddb --username enterprisedb --clone

Upgrade the Postgres version on a node with a different port number
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code:: shell

   pgd node kaolin upgrade --old-bindir /usr/pgsql-16/bin --new-bindir /usr/pgsql-17/bin --old-datadir /var/lib/pgsql/16/data --new-datadir /var/lib/pgsql/17/data --database pgddb --username enterprisedb --old-port 5433 --new-port 5434

Preparing the new data directory ahead of the upgrade
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Use ``--prepare`` to run ``initdb`` and migrate the old cluster’s
configuration into the new data directory, without running
``pg_upgrade`` :

.. code:: shell

   pgd node kaolin upgrade --prepare --old-bindir /usr/pgsql-16/bin --new-bindir /usr/pgsql-17/bin --old-datadir /var/lib/pgsql/16/data --new-datadir /var/lib/pgsql/17/data --database pgddb --username enterprisedb

``--prepare`` is idempotent, so re-running the same command later skips
files that are already in place and only updates what’s changed. This
lets you prepare the new data directory well ahead of a maintenance
window, then perform the actual upgrade later with minimal downtime.

Validating a prepared upgrade before running it
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Once the new data directory is prepared, run the same command with
``--check`` instead of ``--prepare`` to validate that the upgrade would
succeed, without changing any data:

.. code:: shell

   pgd node kaolin upgrade --check --old-bindir /usr/pgsql-16/bin --new-bindir /usr/pgsql-17/bin --old-datadir /var/lib/pgsql/16/data --new-datadir /var/lib/pgsql/17/data --database pgddb --username enterprisedb

If the check succeeds, run the same command again with neither
``--prepare`` nor ``--check`` to perform the upgrade against the
prepared data directory.

Preparing the new data directory with custom configuration files
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The ``--postgresql-conf`` , ``--postgresql-auto-conf`` , and
``--hba-conf`` overrides apply only with ``--prepare`` , since the
regular upgrade and ``--check`` paths use the new data directory’s
existing configuration as is:

.. code:: shell

   pgd node kaolin upgrade --prepare --old-bindir /usr/pgsql-16/bin --new-bindir /usr/pgsql-17/bin --old-datadir /var/lib/pgsql/16/data --new-datadir /var/lib/pgsql/17/data --database pgddb --username enterprisedb --postgresql-conf /opt/new-configs/postgresql.conf --hba-conf /opt/new-configs/pg_hba.conf

To change these overrides after the fact, re-run ``--prepare`` with the
new paths. It’s safe to do this any number of times before the actual
upgrade runs.

Upgrade the Postgres Extended version on a node with Transparent Data Encryption (TDE)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

``--data-encryption`` and its related flags only take effect during
``--prepare`` , which provisions TDE on the new data directory.
``--key-unwrap-command`` and ``--copy-by-block`` are passed again on the
actual upgrade, since that’s the step that decrypts the old data to copy
it block by block into the newly encrypted data directory.

.. code:: shell

   pgd node kaolin upgrade --prepare --database pgddb -B /usr/lib/edb-pge/16/bin --socketdir /var/run/edb-pge/ --old-bindir /usr/lib/edb-pge/15/bin --old-datadir /var/lib/edb-pge/15/main --new-datadir /var/lib/edb-pge/16/main --username postgres --data-encryption --key-wrap-command "openssl enc -aes-128-cbc -pbkdf2 -pass pass:secret -out %p" --key-unwrap-command "openssl enc -d -aes-128-cbc -pbkdf2 -pass pass:secret -in %p"

   pgd node kaolin upgrade --database pgddb -B /usr/lib/edb-pge/16/bin --socketdir /var/run/edb-pge/ --old-bindir /usr/lib/edb-pge/15/bin --old-datadir /var/lib/edb-pge/15/main --new-datadir /var/lib/edb-pge/16/main --username postgres --key-unwrap-command "openssl enc -d -aes-128-cbc -pbkdf2 -pass pass:secret -in %p" --copy-by-block

To preserve the old cluster’s existing encryption key instead of
generating a new one, add
``--copy-key-from <old_datadir>/pg_encryption/key.bin`` to the
``--prepare`` step, alongside ``--key-wrap-command`` or
``--no-key-wrap`` .
