<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://pgstef.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://pgstef.github.io/" rel="alternate" type="text/html" /><updated>2026-04-21T12:51:40+02:00</updated><id>https://pgstef.github.io/feed.xml</id><title type="html">pgstef’s blog</title><subtitle>SELECT * FROM pgstef</subtitle><entry><title type="html">pgBackRest preview - simplifying manual expiration of oldest backups</title><link href="https://pgstef.github.io/2025/12/17/pgbackrest_preview_expire_oldest.html" rel="alternate" type="text/html" title="pgBackRest preview - simplifying manual expiration of oldest backups" /><published>2025-12-17T11:35:00+01:00</published><updated>2025-12-17T11:35:00+01:00</updated><id>https://pgstef.github.io/2025/12/17/pgbackrest_preview_expire_oldest</id><content type="html" xml:base="https://pgstef.github.io/2025/12/17/pgbackrest_preview_expire_oldest.html"><![CDATA[<p>A useful new feature was introduced on 11 December 2025: <strong>Allow expiration of the oldest full backup regardless of current retention</strong>.
Details are available in commit <a href="https://github.com/pgbackrest/pgbackrest/commit/bf2b276dc011464b13039630870934fd42ca473c">bf2b276</a>.</p>

<p>Before this change, it was awkward to expire only the oldest full backup while leaving the existing retention settings untouched.
Users had to temporarily adjust retention (or write a script) to achieve the same result.
Expiring the oldest full backup is particularly helpful when your backup repository is running low on disk space.</p>

<p>Let’s see how this works in practice with a simple example.</p>

<!--MORE-->

<hr />

<h1 id="example-of-expiring-the-oldest-full-backup">Example of expiring the oldest full backup</h1>

<p>Let’s use a very basic demo setup.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pgbackrest info
...
wal archive min/max <span class="o">(</span>18<span class="o">)</span>: 000000010000000000000056/000000010000000300000038

full backup: 20251217-095505F
    wal start/stop: 000000010000000000000056 / 000000010000000000000056

incr backup: 20251217-095505F_20251217-095807I
    wal start/stop: 00000001000000010000001B / 00000001000000010000001B
    backup reference total: 1 full

full backup: 20251217-095902F
    wal start/stop: 000000010000000100000090 / 000000010000000100000090

incr backup: 20251217-095902F_20251217-100124I
    wal start/stop: 000000010000000200000013 / 000000010000000200000013
    backup reference total: 1 full
</code></pre></div></div>

<p>This gives us two full backups, each of them with a linked incremental backup. Note that I have deliberately reduced the output of the <code class="language-plaintext highlighter-rouge">info</code> command to the information relevant to our test case.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pgbackrest expire <span class="nt">--stanza</span><span class="o">=</span>demo <span class="nt">--dry-run</span>
P00   INFO: <span class="o">[</span>DRY-RUN] expire <span class="nb">command </span>begin 2.57.0: ...
P00   INFO: <span class="o">[</span>DRY-RUN] repo1: 18-1 no archive to remove
P00   INFO: <span class="o">[</span>DRY-RUN] expire <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>If we run the <code class="language-plaintext highlighter-rouge">expire</code> command, nothing is expired, as I have set <code class="language-plaintext highlighter-rouge">repo1-retention-full=2</code>, meaning that two full backups should be kept.</p>

<p>Let’s now imagine that your backup repository is running out of disk space and you want to remove the oldest backup to free up some space. How would you do that?</p>

<p>Once you have identified the backup set name to expire (<code class="language-plaintext highlighter-rouge">20251217-095505F</code> in this case), you can run:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pgbackrest expire <span class="nt">--stanza</span><span class="o">=</span>demo <span class="nt">--set</span><span class="o">=</span>20251217-095505F <span class="nt">--dry-run</span>
P00   INFO: <span class="o">[</span>DRY-RUN] expire <span class="nb">command </span>begin 2.57.0: ...
P00   INFO: <span class="o">[</span>DRY-RUN] repo1: expire adhoc backup <span class="nb">set </span>20251217-095505F, 20251217-095505F_20251217-095807I
P00   INFO: <span class="o">[</span>DRY-RUN] repo1: remove expired backup 20251217-095505F_20251217-095807I
P00   INFO: <span class="o">[</span>DRY-RUN] repo1: remove expired backup 20251217-095505F
P00   INFO: <span class="o">[</span>DRY-RUN] expire <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">expire --set</code> option allows you to remove a backup set and all dependent backups (in this case, the incremental one), but the WAL archives are not expired.
Indeed, WAL archives older than the oldest backup are not expired until the retention policy has been met.
This is done to ensure that streaming replicas still have access to older WAL segments that they may need to catch up.</p>

<p>Until this new feature was introduced, the only available option was to adjust the retention settings. In this case, you would count the number of full backups (two), subtract one, and then adjust <code class="language-plaintext highlighter-rouge">--repo1-retention-full=1</code>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pgbackrest expire <span class="nt">--stanza</span><span class="o">=</span>demo <span class="nt">--repo1-retention-full</span><span class="o">=</span>1 <span class="nt">--dry-run</span>
P00   INFO: <span class="o">[</span>DRY-RUN] expire <span class="nb">command </span>begin 2.57.0: ...
P00   INFO: <span class="o">[</span>DRY-RUN] repo1: expire full backup <span class="nb">set </span>20251217-095505F, 20251217-095505F_20251217-095807I
P00   INFO: <span class="o">[</span>DRY-RUN] repo1: remove expired backup 20251217-095505F_20251217-095807I
P00   INFO: <span class="o">[</span>DRY-RUN] repo1: remove expired backup 20251217-095505F
P00   INFO: <span class="o">[</span>DRY-RUN] repo1: 18-1 remove archive, start <span class="o">=</span> 0000000100000000, stop <span class="o">=</span> 00000001000000010000008F
P00   INFO: <span class="o">[</span>DRY-RUN] expire <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>If you compare both outputs, you will see that the WAL archives would now be expired. This is a perfectly valid approach, but let’s be honest: it is not very user-friendly, as it requires knowing the current retention settings and adjusting them manually.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pgbackrest version
pgBackRest 2.58.0dev
</code></pre></div></div>

<p>The new feature introduces a <code class="language-plaintext highlighter-rouge">--oldest</code> option for the <code class="language-plaintext highlighter-rouge">expire</code> command, which expires the oldest full backup set that can be removed (that is, as long as at least one newer full backup remains). This is equivalent to manually decrementing the retention by one, but it is computed automatically. All backups related to the expired full backup set (both differential and incremental) are also expired.
When this option is used, archive retention is also temporarily adjusted so that WAL archives associated with the expired backups can be removed in the same run.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pgbackrest expire <span class="nt">--stanza</span><span class="o">=</span>demo <span class="nt">--oldest</span>
P00   INFO: expire <span class="nb">command </span>begin 2.58.0dev: ...
P00   INFO: repo1: expire full backup <span class="nb">set </span>20251217-095505F, 20251217-095505F_20251217-095807I
P00   INFO: repo1: remove expired backup 20251217-095505F_20251217-095807I
P00   INFO: repo1: remove expired backup 20251217-095505F
P00   INFO: repo1: 18-1 remove archive, start <span class="o">=</span> 0000000100000000, stop <span class="o">=</span> 00000001000000010000008F
P00   INFO: expire <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>This option should be particularly useful for users who want to expire the earliest backup in order to free up space. Since, in this scenario, users will almost always want the corresponding WAL archives to be expired as well, the option internally adjusts archive retention to remove WAL archives that precede the oldest retained full backup.</p>

<p>This option only works if at least one full backup would remain:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pgbackrest info
...
wal archive min/max <span class="o">(</span>18<span class="o">)</span>: 000000010000000100000090/000000010000000300000038

full backup: 20251217-095902F
    wal start/stop: 000000010000000100000090 / 000000010000000100000090

incr backup: 20251217-095902F_20251217-100124I
    wal start/stop: 000000010000000200000013 / 000000010000000200000013
    backup reference total: 1 full

<span class="nv">$ </span>pgbackrest expire <span class="nt">--stanza</span><span class="o">=</span>demo <span class="nt">--oldest</span>
P00   WARN: repo1: expire oldest requested but no eligible full backup to expire
</code></pre></div></div>

<hr />

<h1 id="conclusion">Conclusion</h1>

<p>The <code class="language-plaintext highlighter-rouge">--oldest</code> option addresses a long-standing usability gap by making it trivial to expire the oldest full backup when disk space is tight, without modifying retention settings.
This change comes from a feature request in GitHub issue <a href="https://github.com/pgbackrest/pgbackrest/issues/2666">#2666</a> and should make a previously awkward operation simpler and safer.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[A useful new feature was introduced on 11 December 2025: Allow expiration of the oldest full backup regardless of current retention. Details are available in commit bf2b276. Before this change, it was awkward to expire only the oldest full backup while leaving the existing retention settings untouched. Users had to temporarily adjust retention (or write a script) to achieve the same result. Expiring the oldest full backup is particularly helpful when your backup repository is running low on disk space. Let’s see how this works in practice with a simple example.]]></summary></entry><entry><title type="html">pgBackRest TLS server mode for a primary-standby setup with a repository host</title><link href="https://pgstef.github.io/2025/11/17/pgbackrest_tls_server_mode_primary_standby_repository.html" rel="alternate" type="text/html" title="pgBackRest TLS server mode for a primary-standby setup with a repository host" /><published>2025-11-17T00:00:00+01:00</published><updated>2025-11-17T00:00:00+01:00</updated><id>https://pgstef.github.io/2025/11/17/pgbackrest_tls_server_mode_primary_standby_repository</id><content type="html" xml:base="https://pgstef.github.io/2025/11/17/pgbackrest_tls_server_mode_primary_standby_repository.html"><![CDATA[<p>The TLS server provides an alternative to using SSH for protocol connections to remote hosts.</p>

<p>In this demo setup, the repository host is named <strong>backup-srv</strong>, and the two PostgreSQL nodes participating in <em>Streaming Replication</em> are <strong>pg1-srv</strong> and <strong>pg2-srv</strong>.
All nodes run on AlmaLinux 10.</p>

<p>If you’re familiar with Vagrant, here is a simple <code class="language-plaintext highlighter-rouge">Vagrantfile</code> that provisions three virtual machines using these names:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Vagrantfile</span>
<span class="no">Vagrant</span><span class="p">.</span><span class="nf">configure</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
    <span class="n">config</span><span class="p">.</span><span class="nf">vm</span><span class="p">.</span><span class="nf">box</span> <span class="o">=</span> <span class="s1">'almalinux/10'</span>
    <span class="n">config</span><span class="p">.</span><span class="nf">vm</span><span class="p">.</span><span class="nf">provider</span> <span class="s1">'libvirt'</span> <span class="k">do</span> <span class="o">|</span><span class="n">lv</span><span class="o">|</span>
        <span class="n">lv</span><span class="p">.</span><span class="nf">cpus</span> <span class="o">=</span> <span class="mi">1</span>
        <span class="n">lv</span><span class="p">.</span><span class="nf">memory</span> <span class="o">=</span> <span class="mi">1024</span>
    <span class="k">end</span>

    <span class="c1"># share the default vagrant folder</span>
    <span class="n">config</span><span class="p">.</span><span class="nf">vm</span><span class="p">.</span><span class="nf">synced_folder</span> <span class="s2">"."</span><span class="p">,</span> <span class="s2">"/vagrant"</span><span class="p">,</span> <span class="ss">type: </span><span class="s2">"nfs"</span><span class="p">,</span> <span class="ss">nfs_udp: </span><span class="kp">false</span>

    <span class="n">nodes</span>  <span class="o">=</span> <span class="s1">'backup-srv'</span><span class="p">,</span> <span class="s1">'pg1-srv'</span><span class="p">,</span> <span class="s1">'pg2-srv'</span>
    <span class="n">nodes</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">node</span><span class="o">|</span>
        <span class="n">config</span><span class="p">.</span><span class="nf">vm</span><span class="p">.</span><span class="nf">define</span> <span class="n">node</span> <span class="k">do</span> <span class="o">|</span><span class="n">conf</span><span class="o">|</span>
            <span class="n">conf</span><span class="p">.</span><span class="nf">vm</span><span class="p">.</span><span class="nf">hostname</span> <span class="o">=</span> <span class="n">node</span>
        <span class="k">end</span>
    <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<!--MORE-->

<hr />

<h1 id="installation">Installation</h1>

<p>On all servers, begin by configuring the PGDG repositories:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">-y</span> https://download.postgresql.org/pub/repos/yum/reporpms/EL-10-x86_64/pgdg-redhat-repo-latest.noarch.rpm
</code></pre></div></div>

<p>Then install PostgreSQL on <strong>pg1-srv</strong> and <strong>pg2-srv</strong>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">-y</span> postgresql18-server
</code></pre></div></div>

<p>Create a basic PostgreSQL instance on <strong>pg1-srv</strong>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> /usr/pgsql-18/bin/postgresql-18-setup initdb
<span class="nb">sudo </span>systemctl <span class="nb">enable </span>postgresql-18
<span class="nb">sudo </span>systemctl start postgresql-18
</code></pre></div></div>

<p>Install pgBackRest on every host and confirm the version:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">-y</span> epel-release
<span class="nb">sudo </span>dnf config-manager <span class="nt">--enable</span> crb
<span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">-y</span> pgbackrest
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pgbackrest version
pgBackRest 2.57.0
</code></pre></div></div>

<h2 id="create-a-dedicated-user-on-the-repository-host">Create a dedicated user on the repository host</h2>

<p>The <code class="language-plaintext highlighter-rouge">pgbackrest</code> user will own the backups and WAL archive repository.
Although any user can own the repository, it’s best not to use <code class="language-plaintext highlighter-rouge">postgres</code> to avoid confusion.</p>

<p>Create the user and repository directory on <strong>backup-srv</strong>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo mkdir</span> <span class="nt">-p</span> /backup_space
<span class="nb">sudo mkdir</span> <span class="nt">-p</span> /var/log/pgbackrest

<span class="nb">sudo </span>groupadd pgbackrest
<span class="nb">sudo </span>useradd <span class="nt">-d</span> /backup_space <span class="nt">-M</span> <span class="nt">-g</span> pgbackrest pgbackrest

<span class="nb">sudo chown</span> <span class="nt">-R</span> pgbackrest: /backup_space
<span class="nb">sudo chown</span> <span class="nt">-R</span> pgbackrest: /var/log/pgbackrest
</code></pre></div></div>

<p>For the purpose of this demo, we’ve created <code class="language-plaintext highlighter-rouge">/backup_space</code> to store our backups and archives locally.
The repository can be located on the various supported storage types described in the <a href="https://pgbackrest.org/configuration.html#section-repository">official configuration documentation</a>.</p>

<hr />

<h1 id="certificate-generation">Certificate generation</h1>

<p>The TLS server requires certificates.</p>

<p>A good practical example of generating certificates for PostgreSQL authentication can be found
<a href="https://blog.crunchydata.com/blog/ssl-certificate-authentication-postgresql-docker-containers">here</a>.</p>

<p>Following that approach, generate the certificates in the shared folder used by the Vagrant hosts:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">-y</span> openssl
<span class="nb">cd</span> /vagrant
<span class="nb">mkdir </span>certs <span class="o">&amp;&amp;</span> <span class="nb">cd </span>certs
openssl req <span class="nt">-new</span> <span class="nt">-x509</span> <span class="nt">-days</span> 365 <span class="nt">-nodes</span> <span class="nt">-out</span> ca.crt <span class="nt">-keyout</span> ca.key <span class="nt">-subj</span> <span class="s2">"/CN=root-ca"</span>
openssl req <span class="nt">-new</span> <span class="nt">-nodes</span> <span class="nt">-out</span> backup-srv.csr <span class="nt">-keyout</span> backup-srv.key <span class="nt">-subj</span> <span class="s2">"/CN=backup-srv"</span>
openssl req <span class="nt">-new</span> <span class="nt">-nodes</span> <span class="nt">-out</span> pg1-srv.csr <span class="nt">-keyout</span> pg1-srv.key <span class="nt">-subj</span> <span class="s2">"/CN=pg1-srv"</span>
openssl req <span class="nt">-new</span> <span class="nt">-nodes</span> <span class="nt">-out</span> pg2-srv.csr <span class="nt">-keyout</span> pg2-srv.key <span class="nt">-subj</span> <span class="s2">"/CN=pg2-srv"</span>
openssl x509 <span class="nt">-req</span> <span class="nt">-in</span> backup-srv.csr <span class="nt">-days</span> 365 <span class="nt">-CA</span> ca.crt <span class="nt">-CAkey</span> ca.key <span class="nt">-CAcreateserial</span> <span class="nt">-out</span> backup-srv.crt
openssl x509 <span class="nt">-req</span> <span class="nt">-in</span> pg1-srv.csr <span class="nt">-days</span> 365 <span class="nt">-CA</span> ca.crt <span class="nt">-CAkey</span> ca.key <span class="nt">-CAcreateserial</span> <span class="nt">-out</span> pg1-srv.crt
openssl x509 <span class="nt">-req</span> <span class="nt">-in</span> pg2-srv.csr <span class="nt">-days</span> 365 <span class="nt">-CA</span> ca.crt <span class="nt">-CAkey</span> ca.key <span class="nt">-CAcreateserial</span> <span class="nt">-out</span> pg2-srv.crt
<span class="nb">rm</span> <span class="nt">-f</span> <span class="k">*</span>.csr
</code></pre></div></div>

<p>Deploy the certificates to each server (<code class="language-plaintext highlighter-rouge">ca.crt</code>, <code class="language-plaintext highlighter-rouge">server_name.crt</code>, and <code class="language-plaintext highlighter-rouge">server_name.key</code>):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># on backup-srv</span>
<span class="nb">cd</span> /vagrant/certs
<span class="nb">sudo mkdir</span> <span class="nt">-p</span> /etc/pgbackrest/certs
<span class="nb">sudo cp</span> <span class="sb">`</span><span class="nb">hostname</span><span class="sb">`</span>.<span class="k">*</span> /etc/pgbackrest/certs
<span class="nb">sudo cp </span>ca.crt /etc/pgbackrest/certs
<span class="nb">sudo chown</span> <span class="nt">-R</span> pgbackrest: /etc/pgbackrest/certs
<span class="nb">sudo chmod</span> <span class="nt">-R</span> og-rwx /etc/pgbackrest/certs

<span class="c"># on pg1-srv and pg2-srv</span>
<span class="nb">cd</span> /vagrant/certs
<span class="nb">sudo mkdir</span> <span class="nt">-p</span> /etc/pgbackrest/certs
<span class="nb">sudo cp</span> <span class="sb">`</span><span class="nb">hostname</span><span class="sb">`</span>.<span class="k">*</span> /etc/pgbackrest/certs
<span class="nb">sudo cp </span>ca.crt /etc/pgbackrest/certs
<span class="nb">sudo chown</span> <span class="nt">-R</span> postgres: /etc/pgbackrest/certs
<span class="nb">sudo chmod</span> <span class="nt">-R</span> og-rwx /etc/pgbackrest/certs
</code></pre></div></div>

<hr />

<h1 id="configuration">Configuration</h1>

<p>We will now configure our stanza, named <code class="language-plaintext highlighter-rouge">demo</code>.</p>

<h2 id="repository-server-configuration-backup-srv">Repository server configuration (backup-srv)</h2>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[global]</span>
<span class="c"># repo details
</span><span class="py">repo1-path</span><span class="p">=</span><span class="s">/backup_space</span>
<span class="py">repo1-retention-full</span><span class="p">=</span><span class="s">2</span>
<span class="py">repo1-bundle</span><span class="p">=</span><span class="s">y</span>
<span class="py">repo1-block</span><span class="p">=</span><span class="s">y</span>

<span class="c"># general options
</span><span class="py">process-max</span><span class="p">=</span><span class="s">2</span>
<span class="py">log-level-console</span><span class="p">=</span><span class="s">info</span>
<span class="py">log-level-file</span><span class="p">=</span><span class="s">detail</span>
<span class="py">compress-type</span><span class="p">=</span><span class="s">zst</span>
<span class="py">start-fast</span><span class="p">=</span><span class="s">y</span>
<span class="py">delta</span><span class="p">=</span><span class="s">y</span>

<span class="c"># tls server options
</span><span class="py">tls-server-address</span><span class="p">=</span><span class="s">*</span>
<span class="py">tls-server-cert-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/backup-srv.crt</span>
<span class="py">tls-server-key-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/backup-srv.key</span>
<span class="py">tls-server-ca-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/ca.crt</span>
<span class="py">tls-server-auth</span><span class="p">=</span><span class="s">pg1-srv=demo</span>
<span class="py">tls-server-auth</span><span class="p">=</span><span class="s">pg2-srv=demo</span>

<span class="nn">[demo]</span>
<span class="py">pg1-host</span><span class="p">=</span><span class="s">pg1-srv</span>
<span class="py">pg1-path</span><span class="p">=</span><span class="s">/var/lib/pgsql/18/data</span>
<span class="py">pg1-host-type</span><span class="p">=</span><span class="s">tls</span>
<span class="py">pg1-host-cert-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/backup-srv.crt</span>
<span class="py">pg1-host-key-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/backup-srv.key</span>
<span class="py">pg1-host-ca-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/ca.crt</span>
</code></pre></div></div>

<h2 id="postgresql-servers-pg1-srv-and-pg2-srv">PostgreSQL servers (pg1-srv and pg2-srv)</h2>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[global]</span>
<span class="py">repo1-host</span><span class="p">=</span><span class="s">backup-srv</span>
<span class="py">repo1-host-user</span><span class="p">=</span><span class="s">pgbackrest</span>
<span class="py">repo1-host-type</span><span class="p">=</span><span class="s">tls</span>
<span class="py">repo1-host-cert-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/pg1-srv.crt</span>
<span class="py">repo1-host-key-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/pg1-srv.key</span>
<span class="py">repo1-host-ca-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/ca.crt</span>

<span class="c"># general options
</span><span class="py">compress-type</span><span class="p">=</span><span class="s">zst</span>
<span class="py">process-max</span><span class="p">=</span><span class="s">2</span>
<span class="py">log-level-console</span><span class="p">=</span><span class="s">info</span>
<span class="py">log-level-file</span><span class="p">=</span><span class="s">detail</span>

<span class="c"># tls server options
</span><span class="py">tls-server-address</span><span class="p">=</span><span class="s">*</span>
<span class="py">tls-server-cert-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/pg1-srv.crt</span>
<span class="py">tls-server-key-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/pg1-srv.key</span>
<span class="py">tls-server-ca-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/ca.crt</span>
<span class="py">tls-server-auth</span><span class="p">=</span><span class="s">backup-srv=demo</span>

<span class="nn">[demo]</span>
<span class="py">pg1-path</span><span class="p">=</span><span class="s">/var/lib/pgsql/18/data</span>
</code></pre></div></div>

<p><em>(Adjust <code class="language-plaintext highlighter-rouge">pg1-srv</code> to <code class="language-plaintext highlighter-rouge">pg2-srv</code> on <strong>pg2-srv</strong>.)</em></p>

<p>The idea is that each host runs a TLS server to handle requests from the others.
For example:</p>

<ul>
  <li>The <code class="language-plaintext highlighter-rouge">backup</code> command on the repository host acts as a TLS client to the PostgreSQL node.</li>
  <li>The <code class="language-plaintext highlighter-rouge">archive-push</code> command on the PostgreSQL node acts as a TLS client to the repository host.</li>
</ul>

<p>The PGDG <a href="https://github.com/pgdg-packaging/pgdg-rpms/blob/master/rpm/redhat/main/common/pgbackrest/main/pgbackrest.spec">packages</a> provide a <code class="language-plaintext highlighter-rouge">pgbackrest.service</code> systemd unit to start and stop the TLS server.</p>

<h2 id="override-the-service-on-backup-srv">Override the service on backup-srv</h2>

<p>Create a drop-in override using:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl edit pgbackrest
</code></pre></div></div>

<p>Add:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Service]</span>
<span class="py">User</span><span class="p">=</span><span class="s">pgbackrest</span>
<span class="py">Group</span><span class="p">=</span><span class="s">pgbackrest</span>
</code></pre></div></div>

<p>Reload systemd:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl daemon-reload
</code></pre></div></div>

<p>Start the TLS server on all hosts:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl <span class="nb">enable </span>pgbackrest
<span class="nb">sudo </span>systemctl start pgbackrest
pgbackrest server-ping
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">server-ping</code> command checks the server’s availability (no authentication is performed).</p>

<h2 id="configure-postgresql-archiving-on-pg1-srv">Configure PostgreSQL archiving on pg1-srv</h2>

<p>In <code class="language-plaintext highlighter-rouge">postgresql.conf</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>listen_addresses = '*'
archive_mode = on
archive_command = 'pgbackrest --stanza=demo archive-push %p'
</code></pre></div></div>

<p>Restart PostgreSQL:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl restart postgresql-18.service
</code></pre></div></div>

<h2 id="create-the-stanza-and-validate-configuration">Create the stanza and validate configuration</h2>

<p>On <strong>backup-srv</strong>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> <span class="nt">-iu</span> pgbackrest pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo stanza-create
<span class="nb">sudo</span> <span class="nt">-iu</span> pgbackrest pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo check
</code></pre></div></div>

<hr />

<h1 id="perform-a-backup">Perform a backup</h1>

<p>You can now take your first backup:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> pgbackrest pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo <span class="nt">--type</span><span class="o">=</span>full backup
P00   INFO: backup <span class="nb">command </span>begin 2.57.0: ...
P00   INFO: execute non-exclusive backup start:
            backup begins after the requested immediate checkpoint completes
P00   INFO: backup start archive <span class="o">=</span> 000000010000000000000003, lsn <span class="o">=</span> 0/3000028
P00   INFO: check archive <span class="k">for </span>prior segment 000000010000000000000002
P00   INFO: execute non-exclusive backup stop and <span class="nb">wait </span><span class="k">for </span>all WAL segments to archive
P00   INFO: backup stop archive <span class="o">=</span> 000000010000000000000003, lsn <span class="o">=</span> 0/3000120
P00   INFO: check archive <span class="k">for </span>segment<span class="o">(</span>s<span class="o">)</span> 000000010000000000000003:000000010000000000000003
P00   INFO: new backup label <span class="o">=</span> 20251117-134141F
P00   INFO: full backup size <span class="o">=</span> 22.7MB, file total <span class="o">=</span> 970
P00   INFO: backup <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>You can run the <code class="language-plaintext highlighter-rouge">info</code> command from any server correctly configured with pgBackRest.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># From the PostgreSQL node</span>
<span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo info
stanza: demo
    status: ok
    cipher: none

    db <span class="o">(</span>current<span class="o">)</span>
        wal archive min/max <span class="o">(</span>18<span class="o">)</span>: 000000010000000000000003/000000010000000000000003

        full backup: 20251117-134141F
            timestamp start/stop: 2025-11-17 13:41:41+00 / 2025-11-17 13:41:43+00
            wal start/stop: 000000010000000000000003 / 000000010000000000000003
            database size: 22.7MB, database backup size: 22.7MB
            repo1: backup size: 2.7MB

<span class="c"># From the backup server</span>
<span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> pgbackrest pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo info
stanza: demo
    status: ok
    cipher: none

    db <span class="o">(</span>current<span class="o">)</span>
        wal archive min/max <span class="o">(</span>18<span class="o">)</span>: 000000010000000000000003/000000010000000000000003

        full backup: 20251117-134141F
            timestamp start/stop: 2025-11-17 13:41:41+00 / 2025-11-17 13:41:43+00
            wal start/stop: 000000010000000000000003 / 000000010000000000000003
            database size: 22.7MB, database backup size: 22.7MB
            repo1: backup size: 2.7MB
</code></pre></div></div>

<hr />

<h1 id="prepare-the-servers-for-streaming-replication">Prepare the servers for Streaming Replication</h1>

<p>On <strong>pg1-srv</strong>, create a replication user:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres psql
<span class="nv">postgres</span><span class="o">=</span><span class="c"># CREATE ROLE replic_user WITH LOGIN REPLICATION PASSWORD 'mypwd';</span>
</code></pre></div></div>

<p>Edit <code class="language-plaintext highlighter-rouge">pg_hba.conf</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>host   replication   replic_user   pg2-srv   scram-sha-256
</code></pre></div></div>

<p>Reload:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl reload postgresql-18.service
</code></pre></div></div>

<p>Configure <code class="language-plaintext highlighter-rouge">.pgpass</code> on <strong>pg2-srv</strong>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>sh <span class="nt">-c</span> <span class="s2">"echo '*:*:replication:replic_user:mypwd' &gt;&gt; ~postgres/.pgpass"</span>
<span class="nb">sudo chown </span>postgres: ~postgres/.pgpass
<span class="nb">sudo chmod </span>0600 ~postgres/.pgpass
</code></pre></div></div>

<hr />

<h1 id="set-up-the-standby-server">Set up the standby server</h1>

<p>Modify <code class="language-plaintext highlighter-rouge">/etc/pgbackrest.conf</code> on <strong>pg2-srv</strong> to include:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">recovery-option</span><span class="p">=</span><span class="s">primary_conninfo=host=pg1-srv user=replic_user</span>
</code></pre></div></div>

<p>Then verify the configuration by running the <code class="language-plaintext highlighter-rouge">info</code> command. It should print the same output as above.</p>

<p>Restore the backup:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo <span class="nt">--type</span><span class="o">=</span>standby restore
P00   INFO: restore <span class="nb">command </span>begin 2.57.0: ...
P00   INFO: repo1: restore backup <span class="nb">set </span>20251117-134141F, recovery will start at ...
P00   INFO: write updated /var/lib/pgsql/18/data/postgresql.auto.conf
P00   INFO: restore global/pg_control
            <span class="o">(</span>performed last to ensure aborted restores cannot be started<span class="o">)</span>
P00   INFO: restore size <span class="o">=</span> 22.7MB, file total <span class="o">=</span> 970
P00   INFO: restore <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>This adds the required recovery settings to <code class="language-plaintext highlighter-rouge">postgresql.auto.conf</code>:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Recovery settings generated by pgBackRest restore
</span><span class="py">primary_conninfo</span> <span class="p">=</span> <span class="s">'host=pg1-srv user=replic_user'</span>
<span class="py">restore_command</span> <span class="p">=</span> <span class="s">'pgbackrest --stanza=demo archive-get %f "%p"'</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">--type=standby</code> option creates the <code class="language-plaintext highlighter-rouge">standby.signal</code> needed for PostgreSQL to start in standby mode. All we have to do now is start PostgreSQL:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl <span class="nb">enable </span>postgresql-18
<span class="nb">sudo </span>systemctl start postgresql-18
</code></pre></div></div>

<p>If replication is configured correctly, you should see a <code class="language-plaintext highlighter-rouge">walsender</code> process on <strong>pg1-srv</strong>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres ps <span class="nt">-o</span> pid,cmd fx
    PID CMD
   5768 ps <span class="nt">-o</span> pid,cmd fx
   5408 /usr/pgsql-18/bin/postgres <span class="nt">-D</span> /var/lib/pgsql/18/data/
   5409  <span class="se">\_</span> postgres: logger
   5410  <span class="se">\_</span> postgres: io worker 0
   5411  <span class="se">\_</span> postgres: io worker 1
   5412  <span class="se">\_</span> postgres: io worker 2
   5413  <span class="se">\_</span> postgres: checkpointer
   5414  <span class="se">\_</span> postgres: background writer
   5416  <span class="se">\_</span> postgres: walwriter
   5417  <span class="se">\_</span> postgres: autovacuum launcher
   5418  <span class="se">\_</span> postgres: archiver last was 000000010000000000000003.00000028.backup
   5419  <span class="se">\_</span> postgres: logical replication launcher
   5752  <span class="se">\_</span> postgres: walsender replic_user 192.168.121.28<span class="o">(</span>48720<span class="o">)</span> streaming 0/4001A40
   5376 /usr/bin/pgbackrest server
</code></pre></div></div>

<p>We now have a two-node cluster using <em>Streaming Replication</em> with WAL archiving as a fallback.</p>

<hr />

<h1 id="take-backups-from-the-standby-server">Take backups from the standby server</h1>

<p>Extend the configuration on <strong>backup-srv</strong>:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">pg2-host</span><span class="p">=</span><span class="s">pg2-srv</span>
<span class="py">pg2-path</span><span class="p">=</span><span class="s">/var/lib/pgsql/18/data</span>
<span class="py">pg2-host-type</span><span class="p">=</span><span class="s">tls</span>
<span class="py">pg2-host-cert-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/backup-srv.crt</span>
<span class="py">pg2-host-key-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/backup-srv.key</span>
<span class="py">pg2-host-ca-file</span><span class="p">=</span><span class="s">/etc/pgbackrest/certs/ca.crt</span>
<span class="py">backup-standby</span><span class="p">=</span><span class="s">y</span>
</code></pre></div></div>

<p>Now take a new backup, where the data files will come from the standby:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> pgbackrest pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo <span class="nt">--type</span><span class="o">=</span>full backup
P00   INFO: backup <span class="nb">command </span>begin 2.57.0: ...
P00   INFO: execute non-exclusive backup start:
            backup begins after the requested immediate checkpoint completes
P00   INFO: backup start archive <span class="o">=</span> 000000010000000000000005, lsn <span class="o">=</span> 0/5000028
P00   INFO: <span class="nb">wait </span><span class="k">for </span>replay on the standby to reach 0/5000028
P00   INFO: replay on the standby reached 0/5000028
P00   INFO: check archive <span class="k">for </span>prior segment 000000010000000000000004
P00   INFO: execute non-exclusive backup stop and <span class="nb">wait </span><span class="k">for </span>all WAL segments to archive
P00   INFO: backup stop archive <span class="o">=</span> 000000010000000000000005, lsn <span class="o">=</span> 0/5000120
P00   INFO: check archive <span class="k">for </span>segment<span class="o">(</span>s<span class="o">)</span> 000000010000000000000005:000000010000000000000005
P00   INFO: new backup label <span class="o">=</span> 20251117-135549F
P00   INFO: full backup size <span class="o">=</span> 22.7MB, file total <span class="o">=</span> 970
</code></pre></div></div>

<hr />

<h1 id="conclusion">Conclusion</h1>

<p>The TLS server offers a highly performant alternative to SSH for remote operations.
It fits perfectly in containerized environments, where it can significantly improve the user experience.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[The TLS server provides an alternative to using SSH for protocol connections to remote hosts. In this demo setup, the repository host is named backup-srv, and the two PostgreSQL nodes participating in Streaming Replication are pg1-srv and pg2-srv. All nodes run on AlmaLinux 10. If you’re familiar with Vagrant, here is a simple Vagrantfile that provisions three virtual machines using these names: # Vagrantfile Vagrant.configure(2) do |config| config.vm.box = 'almalinux/10' config.vm.provider 'libvirt' do |lv| lv.cpus = 1 lv.memory = 1024 end # share the default vagrant folder config.vm.synced_folder ".", "/vagrant", type: "nfs", nfs_udp: false nodes = 'backup-srv', 'pg1-srv', 'pg2-srv' nodes.each do |node| config.vm.define node do |conf| conf.vm.hostname = node end end end]]></summary></entry><entry><title type="html">Combining cloud storage and dedicated backup host with pgBackRest</title><link href="https://pgstef.github.io/2024/11/28/combining_cloud_storage_and_dedicated_backup_host_with_pgbackrest.html" rel="alternate" type="text/html" title="Combining cloud storage and dedicated backup host with pgBackRest" /><published>2024-11-28T08:30:00+01:00</published><updated>2024-11-28T08:30:00+01:00</updated><id>https://pgstef.github.io/2024/11/28/combining_cloud_storage_and_dedicated_backup_host_with_pgbackrest</id><content type="html" xml:base="https://pgstef.github.io/2024/11/28/combining_cloud_storage_and_dedicated_backup_host_with_pgbackrest.html"><![CDATA[<p><strong>pgBackRest</strong> is a popular backup and restore tool for PostgreSQL, known for easily handling even the largest databases and workloads. It’s packed with powerful features, but all that flexibility can sometimes feel a bit overwhelming.</p>

<p>In my earlier posts, I showed how to take <a href="https://pgstef.github.io/2024/09/23/pgbackrest_backups_from_the_standby_server.html">backups from a standby server</a> and how to <a href="https://pgstef.github.io/2024/09/25/pgbackrest_dedicated_backup_host.html">set up a dedicated backup host</a>. But there’s another great feature we haven’t explored yet: pgBackRest can store backups in cloud storage like S3, Azure, or Google Cloud.</p>

<p>Most people choose one option: either a backup host or cloud storage. But did you know you can use both at the same time? This gives you even more flexibility in your backup strategy.</p>

<p>Let’s pick up where we left off. We’ve got a PostgreSQL cluster with a primary server (pg1), a standby server (pg2), and a backup server (repo1) storing backups and WAL archives on an NFS mount. Today, we’ll take it a step further by adding an S3 bucket to the setup :-)</p>

<!--MORE-->

<hr />

<h1 id="example-setup-s3-bucket-with-a-repository-host">Example setup: S3 bucket with a repository host</h1>

<h2 id="initial-situation">Initial situation</h2>

<p>As a reminder, here’s the initial situation and configuration.</p>

<p><strong>pg1</strong> is our primary:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>postgres@pg1 ~]<span class="nv">$ </span>ps <span class="nt">-o</span> pid,cmd fx
    PID CMD
    764 /usr/pgsql-16/bin/postgres <span class="nt">-D</span> /var/lib/pgsql/16/data/
    814  <span class="se">\_</span> postgres: logger 
    824  <span class="se">\_</span> postgres: checkpointer 
    825  <span class="se">\_</span> postgres: background writer 
    834  <span class="se">\_</span> postgres: walwriter 
    835  <span class="se">\_</span> postgres: autovacuum launcher 
    836  <span class="se">\_</span> postgres: archiver last was 000000010000000000000060
    837  <span class="se">\_</span> postgres: logical replication launcher 
    841  <span class="se">\_</span> postgres: walsender replicator 192.168.121.66<span class="o">(</span>38334<span class="o">)</span> streaming 0/610001C0
</code></pre></div></div>

<p><strong>pg2</strong> is our standby server:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>postgres@pg2 ~]<span class="nv">$ </span>ps <span class="nt">-o</span> pid,cmd fx
    PID CMD
    744 /usr/pgsql-16/bin/postgres <span class="nt">-D</span> /var/lib/pgsql/16/data/
    821  <span class="se">\_</span> postgres: logger 
    831  <span class="se">\_</span> postgres: checkpointer 
    832  <span class="se">\_</span> postgres: background writer 
    833  <span class="se">\_</span> postgres: startup recovering 000000010000000000000061
    870  <span class="se">\_</span> postgres: walreceiver streaming 0/610001C0
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">/etc/pgbackrest.conf</code> configuration is exactly the same on both nodes:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[global]</span>
<span class="py">repo1-host</span><span class="p">=</span><span class="s">repo1</span>
<span class="py">repo1-host-user</span><span class="p">=</span><span class="s">pgbackrest</span>
<span class="py">log-level-console</span><span class="p">=</span><span class="s">info</span>
<span class="py">log-level-file</span><span class="p">=</span><span class="s">detail</span>
<span class="py">compress-type</span><span class="p">=</span><span class="s">zst</span>

<span class="nn">[mycluster]</span>
<span class="py">pg1-path</span><span class="p">=</span><span class="s">/var/lib/pgsql/16/data</span>
</code></pre></div></div>

<p><strong>repo1</strong> is our dedicated backup host and there’s one full backup in the repository:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>pgbackrest@repo1 ~]<span class="nv">$ </span>pgbackrest info
stanza: mycluster
    status: ok
    cipher: none

    db <span class="o">(</span>current<span class="o">)</span>
        wal archive min/max <span class="o">(</span>16<span class="o">)</span>: 000000010000000000000062/000000010000000000000062

        full backup: 20241127-125209F
            timestamp start/stop: 2024-11-27 12:52:09+00 / 2024-11-27 12:52:13+00
            wal start/stop: 000000010000000000000062 / 000000010000000000000062
            database size: 1.5GB, database backup size: 1.5GB
            repo1: backup size: 70.3MB
</code></pre></div></div>

<p>Here’s the <code class="language-plaintext highlighter-rouge">/etc/pgbackrest.conf</code> configuration for this node:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[global]</span>
<span class="py">repo1-path</span><span class="p">=</span><span class="s">/shared/pgbackrest</span>
<span class="py">repo1-retention-full</span><span class="p">=</span><span class="s">4</span>
<span class="py">repo1-bundle</span><span class="p">=</span><span class="s">y</span>
<span class="py">repo1-block</span><span class="p">=</span><span class="s">y</span>
<span class="py">start-fast</span><span class="p">=</span><span class="s">y</span>
<span class="py">log-level-console</span><span class="p">=</span><span class="s">info</span>
<span class="py">log-level-file</span><span class="p">=</span><span class="s">detail</span>
<span class="py">delta</span><span class="p">=</span><span class="s">y</span>
<span class="py">process-max</span><span class="p">=</span><span class="s">2</span>
<span class="py">compress-type</span><span class="p">=</span><span class="s">zst</span>
<span class="py">backup-standby</span><span class="p">=</span><span class="s">y</span>

<span class="nn">[mycluster]</span>
<span class="py">pg1-path</span><span class="p">=</span><span class="s">/var/lib/pgsql/16/data</span>
<span class="py">pg1-host</span><span class="p">=</span><span class="s">pg1</span>
<span class="py">pg1-host-user</span><span class="p">=</span><span class="s">postgres</span>
<span class="py">pg2-path</span><span class="p">=</span><span class="s">/var/lib/pgsql/16/data</span>
<span class="py">pg2-host</span><span class="p">=</span><span class="s">pg2</span>
<span class="py">pg2-host-user</span><span class="p">=</span><span class="s">postgres</span>
</code></pre></div></div>

<p>The repository <code class="language-plaintext highlighter-rouge">repo1-path=/shared/pgbackrest</code> is currently an NFS mount point.
Let’s adjust this setting to point to an S3 bucket named <code class="language-plaintext highlighter-rouge">myversionedbucket</code> with the following configuration:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">repo1-path</span><span class="p">=</span><span class="s">/demo-repo</span>
<span class="py">repo1-type</span><span class="p">=</span><span class="s">s3</span>
<span class="py">repo1-s3-bucket</span><span class="p">=</span><span class="s">myversionedbucket</span>
<span class="py">repo1-s3-key</span><span class="p">=</span><span class="s">accessKey1</span>
<span class="py">repo1-s3-key-secret</span><span class="p">=</span><span class="s">verySecretKey1</span>
<span class="py">repo1-s3-region</span><span class="p">=</span><span class="s">us-east-1</span>
<span class="py">repo1-s3-endpoint</span><span class="p">=</span><span class="s">s3.us-east-1.amazonaws.com</span>
</code></pre></div></div>

<p>As the new repository is empty, we need to initialize it and use the <code class="language-plaintext highlighter-rouge">stanza-create</code> command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>pgbackrest@repo1 ~]<span class="nv">$ </span>pgbackrest <span class="nt">--stanza</span><span class="o">=</span>mycluster stanza-create
INFO: stanza-create <span class="k">for </span>stanza <span class="s1">'mycluster'</span> on repo1
INFO: stanza-create <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>Then, check that the archiving process is still functioning correctly. WAL archives should now be pushed from the primary to the S3 bucket. Remember, the archives are still routed through our dedicated backup host, as we haven’t changed the PostgreSQL node configurations!</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>pgbackrest@repo1 ~]<span class="nv">$ </span>pgbackrest <span class="nt">--stanza</span><span class="o">=</span>mycluster check
INFO: check repo1 configuration <span class="o">(</span>primary<span class="o">)</span>
INFO: check repo1 archive <span class="k">for </span>WAL <span class="o">(</span>primary<span class="o">)</span>
INFO: WAL segment 000000010000000000000063 successfully archived to <span class="s1">'...'</span> on repo1
INFO: check <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>Let’s take a backup now:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>pgbackrest@repo1 ~]<span class="nv">$ </span>pgbackrest <span class="nt">--stanza</span><span class="o">=</span>mycluster backup <span class="nt">--type</span><span class="o">=</span>full
INFO: backup <span class="nb">command </span>begin ...
INFO: execute non-exclusive backup start:
	backup begins after the requested immediate checkpoint completes
INFO: backup start archive <span class="o">=</span> 000000010000000000000065, lsn <span class="o">=</span> 0/65000028
INFO: <span class="nb">wait </span><span class="k">for </span>replay on the standby to reach 0/65000028
INFO: replay on the standby reached 0/65000028
INFO: check archive <span class="k">for </span>prior segment 000000010000000000000064
INFO: execute non-exclusive backup stop and <span class="nb">wait </span><span class="k">for </span>all WAL segments to archive
INFO: backup stop archive <span class="o">=</span> 000000010000000000000065, lsn <span class="o">=</span> 0/65000100
INFO: check archive <span class="k">for </span>segment<span class="o">(</span>s<span class="o">)</span> 000000010000000000000065:000000010000000000000065
INFO: new backup label <span class="o">=</span> 20241127-135531F
INFO: full backup size <span class="o">=</span> 1.5GB, file total <span class="o">=</span> 1283
INFO: backup <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>Finally, here’s what our current setup looks like:</p>

<p><img src="../../../images/20241128-repo-host-s3-1.png" alt="" /></p>

<h2 id="what-happens-if-the-repository-host-is-down">What happens if the repository host is down?</h2>

<p>With <strong>repo1</strong> down, the PostgreSQL nodes can’t reach the backup storage anymore…</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>postgres@pg1 ~]<span class="nv">$ </span>pgbackrest info
stanza: <span class="o">[</span>invalid]
    status: error <span class="o">(</span>other<span class="o">)</span>
            <span class="o">[</span>UnknownError] remote-0 process on <span class="s1">'repo1'</span> terminated unexpectedly
            <span class="o">[</span>255]: ssh: connect to host repo1 port 22: No route to host
</code></pre></div></div>

<p>It also means that the WAL archiver process is blocked:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>postgres@pg1 ~]<span class="nv">$ </span>psql <span class="nt">-x</span> <span class="nt">-c</span> <span class="s2">"select * from pg_stat_archiver;"</span>
-[ RECORD 1 <span class="o">]</span><span class="nt">------</span>+-----------------------------------------
failed_count       | 12
last_failed_wal    | 000000010000000000000066
last_failed_time   | 2024-11-27 13:58:16.547647+00
</code></pre></div></div>

<p>Obviously, you’re not triggering backups every 5 minutes, so having the repository host down for a while might not seem like a big issue at first. However, the magic of PostgreSQL’s <em>Point-In-Time Recovery</em> relies on continuous WAL archiving. If the archiver process gets blocked, PostgreSQL will start storing all the WAL files locally in its <code class="language-plaintext highlighter-rouge">pg_wal</code> directory until the issue is resolved. This can quickly lead to disk space problems and, in the worst-case scenario, bring your cluster down.</p>

<p>Thankfully, pgBackRest offers the <a href="https://pgbackrest.org/configuration.html#section-archive/option-archive-push-queue-max"><code class="language-plaintext highlighter-rouge">archive-push-queue-max</code></a>  setting to help manage this situation. But why stop there? We can get creative and take advantage of pgBackRest’s flexibility by attaching the S3 bucket directly to the PostgreSQL nodes…</p>

<h2 id="attaching-the-s3-bucket-directly-to-postgresql-nodes">Attaching the S3 bucket directly to PostgreSQL nodes</h2>

<p>As a reminder, on <strong>pg1</strong> and <strong>pg2</strong>, the repository is currently defined as:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">repo1-host</span><span class="p">=</span><span class="s">repo1</span>
<span class="py">repo1-host-user</span><span class="p">=</span><span class="s">pgbackrest</span>
</code></pre></div></div>

<p>We’ll replace these settings with the S3 bucket configuration:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">repo1-path</span><span class="p">=</span><span class="s">/demo-repo</span>
<span class="py">repo1-type</span><span class="p">=</span><span class="s">s3</span>
<span class="py">repo1-s3-bucket</span><span class="p">=</span><span class="s">myversionedbucket</span>
<span class="py">repo1-s3-key</span><span class="p">=</span><span class="s">accessKey1</span>
<span class="py">repo1-s3-key-secret</span><span class="p">=</span><span class="s">verySecretKey1</span>
<span class="py">repo1-s3-region</span><span class="p">=</span><span class="s">us-east-1</span>
<span class="py">repo1-s3-endpoint</span><span class="p">=</span><span class="s">s3.us-east-1.amazonaws.com</span>
</code></pre></div></div>

<p>This change allows us to view the repository content and unblock the archiver process:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>postgres@pg1 ~]<span class="nv">$ </span>pgbackrest info
stanza: mycluster
    status: ok
    cipher: none

    db <span class="o">(</span>current<span class="o">)</span>
        wal archive min/max <span class="o">(</span>16<span class="o">)</span>: 000000010000000000000063/000000010000000000000065

        full backup: 20241127-135531F
            timestamp start/stop: 2024-11-27 13:55:31+00 / 2024-11-27 13:55:35+00
            wal start/stop: 000000010000000000000065 / 000000010000000000000065
            database size: 1.5GB, database backup size: 1.5GB
            repo1: backup size: 70.3MB

<span class="o">[</span>postgres@pg1 ~]<span class="nv">$ </span>pgbackrest <span class="nt">--stanza</span><span class="o">=</span>mycluster check
INFO: check repo1 configuration <span class="o">(</span>primary<span class="o">)</span>
INFO: check repo1 archive <span class="k">for </span>WAL <span class="o">(</span>primary<span class="o">)</span>
INFO: WAL segment 000000010000000000000067 successfully archived to <span class="s1">'...'</span> on repo1
INFO: check <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>Now that we’ve connected the PostgreSQL nodes to the S3 bucket, we can trigger a backup from any node. On the primary server (<strong>pg1</strong>), let’s first add the pgBackRest backup configuration (as defined earlier on the repository host, <strong>repo1</strong>):</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">repo1-retention-full</span><span class="p">=</span><span class="s">4</span>
<span class="py">repo1-bundle</span><span class="p">=</span><span class="s">y</span>
<span class="py">repo1-block</span><span class="p">=</span><span class="s">y</span>
<span class="py">start-fast</span><span class="p">=</span><span class="s">y</span>
<span class="py">delta</span><span class="p">=</span><span class="s">y</span>
<span class="py">process-max</span><span class="p">=</span><span class="s">2</span>
</code></pre></div></div>

<p>Finally, trigger an incremental backup:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>postgres@pg1 ~]<span class="nv">$ </span>pgbackrest <span class="nt">--stanza</span><span class="o">=</span>mycluster backup <span class="nt">--type</span><span class="o">=</span>incr
INFO: backup <span class="nb">command </span>begin ...
INFO: last backup label <span class="o">=</span> 20241127-135531F
...
INFO: backup <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>Here’s what our updated setup looks like:</p>

<p><img src="../../../images/20241128-repo-host-s3-2.png" alt="" /></p>

<h2 id="what-happens-when-the-repository-host-is-back-online">What happens when the repository host is back online?</h2>

<p>Once the repository host is back online, it will regain access to the PostgreSQL nodes and can resume taking backups as usual. Since all nodes, including the repository host, point to the same repository location (the S3 bucket), pgBackRest will seamlessly pick up where it left off, ensuring smooth and uninterrupted operation.</p>

<hr />

<h1 id="conclusion">Conclusion</h1>

<p>Routing archiving directly to S3 helped avoid potential disk space issues on the PostgreSQL nodes caused by accumulating WAL files. It also introduced the flexibility to trigger backups from any node, ensuring the cluster remains operational even if one node fails.</p>

<p>However, from a security perspective, this setup exposes your S3 bucket’s content to all nodes, requiring careful management of access credentials. It also adds the responsibility of keeping configuration files synchronized when making changes, such as updating retention settings.</p>

<p>Additionally, if <a href="https://pgbackrest.org/configuration.html#section-repository/option-repo-cipher-type">encryption</a> is used, the key must be present on all nodes, further increasing the risk of exposure.</p>

<p>Ultimately, finding the right balance between security and availability is the key challenge, and pgBackRest’s flexibility helps you deal with it ;-)</p>]]></content><author><name></name></author><summary type="html"><![CDATA[pgBackRest is a popular backup and restore tool for PostgreSQL, known for easily handling even the largest databases and workloads. It’s packed with powerful features, but all that flexibility can sometimes feel a bit overwhelming. In my earlier posts, I showed how to take backups from a standby server and how to set up a dedicated backup host. But there’s another great feature we haven’t explored yet: pgBackRest can store backups in cloud storage like S3, Azure, or Google Cloud. Most people choose one option: either a backup host or cloud storage. But did you know you can use both at the same time? This gives you even more flexibility in your backup strategy. Let’s pick up where we left off. We’ve got a PostgreSQL cluster with a primary server (pg1), a standby server (pg2), and a backup server (repo1) storing backups and WAL archives on an NFS mount. Today, we’ll take it a step further by adding an S3 bucket to the setup :-)]]></summary></entry><entry><title type="html">pgBackRest dedicated backup host</title><link href="https://pgstef.github.io/2024/09/25/pgbackrest_dedicated_backup_host.html" rel="alternate" type="text/html" title="pgBackRest dedicated backup host" /><published>2024-09-25T10:00:00+02:00</published><updated>2024-09-25T10:00:00+02:00</updated><id>https://pgstef.github.io/2024/09/25/pgbackrest_dedicated_backup_host</id><content type="html" xml:base="https://pgstef.github.io/2024/09/25/pgbackrest_dedicated_backup_host.html"><![CDATA[<p>As I mentioned in my last blog post, as your cluster grows with multiple standby servers and potentially automated failover (using tools like Patroni), it becomes more practical to set up a dedicated repository host, also known as a dedicated backup server. This backup server can then trigger backups and automatically select the appropriate node in case of failover, eliminating the need for manual intervention.</p>

<p>In this post, I’ll show you how easy it is to add a repository host to an existing cluster. I’ll also give you a sneak peek at a new feature expected to be included in the next pgBackRest release 😉</p>

<!--MORE-->

<hr />

<h1 id="example-setup-for-repository-host">Example setup for repository host</h1>

<h2 id="initial-situation">Initial situation</h2>

<p>In this example, we pick up from where we left off <a href="https://pgstef.github.io/2024/09/23/pgbackrest_backups_from_the_standby_server.html">last time</a>: a primary server (<code class="language-plaintext highlighter-rouge">pg1</code>) with a standby (<code class="language-plaintext highlighter-rouge">pg2</code>), both already configured to use pgBackRest (with an NFS mount) for backups taken from the standby. Now, we will add a new node, <code class="language-plaintext highlighter-rouge">repo1</code>, to take over pgBackRest backups.</p>

<p>The pgBackRest <a href="https://pgbackrest.org/user-guide-rhel.html#repo-host">user guide</a> provides a comprehensive overview of how to set up a repository host. Since pgBackRest needs to interact with local processes on each node, we must enable communication between the hosts, either through passwordless SSH or <a href="https://pgbackrest.org/user-guide-rhel.html#repo-host/config">TLS with client certificates</a>. While SSH is generally easier to set up, TLS offers better performance. If you’re interested in an example of the TLS setup, I wrote <a href="https://pgstef.github.io/2022/02/21/pgbackrest_tls_server.html">this blog post</a> when the feature was first introduced.</p>

<h2 id="installation">Installation</h2>

<p>Let’s return to our repository host setup. The first step, of course, is to install pgBackRest:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>dnf <span class="nb">install </span>pgbackrest <span class="nt">-y</span>
</code></pre></div></div>

<p>Any user can own the repository, but it’s best to avoid using the <code class="language-plaintext highlighter-rouge">postgres</code> user (if it exists) to prevent confusion. Instead, let’s create a dedicated system user for this purpose:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>groupadd pgbackrest
<span class="nv">$ </span><span class="nb">sudo </span>adduser <span class="nt">-g</span> pgbackrest <span class="nt">-n</span> pgbackrest
<span class="nv">$ </span><span class="nb">sudo chown</span> <span class="nt">-R</span> pgbackrest: /var/log/pgbackrest/
</code></pre></div></div>

<p>The SSH setup is up to you, but usually it is as simple as creating SSH keys and authorize them on the other nodes. Example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># From repo1</span>
<span class="o">[</span>pgbackrest@repo1 ~]<span class="nv">$ </span>ssh-keygen <span class="nt">-f</span> /home/pgbackrest/.ssh/id_rsa <span class="nt">-t</span> rsa <span class="nt">-b</span> 4096 <span class="nt">-N</span> <span class="s2">""</span>
<span class="o">[</span>pgbackrest@repo1 ~]<span class="nv">$ </span>ssh-copy-id postgres@pg1
<span class="o">[</span>pgbackrest@repo1 ~]<span class="nv">$ </span>ssh postgres@pg1 <span class="nb">hostname</span>
<span class="o">[</span>pgbackrest@repo1 ~]<span class="nv">$ </span>ssh-copy-id postgres@pg2
<span class="o">[</span>pgbackrest@repo1 ~]<span class="nv">$ </span>ssh postgres@pg2 <span class="nb">hostname</span>

<span class="c"># From pg1</span>
<span class="o">[</span>postgres@pg1 ~]<span class="nv">$ </span>ssh-copy-id pgbackrest@repo1
<span class="o">[</span>postgres@pg1 ~]<span class="nv">$ </span>ssh pgbackrest@repo1 <span class="nb">hostname</span>

<span class="c"># From pg2</span>
<span class="o">[</span>postgres@pg2 ~]<span class="nv">$ </span>ssh-copy-id pgbackrest@repo1
<span class="o">[</span>postgres@pg2 ~]<span class="nv">$ </span>ssh pgbackrest@repo1 <span class="nb">hostname</span>
</code></pre></div></div>

<p>The repository can be stored on any supported <a href="https://pgbackrest.org/configuration.html#section-repository/option-repo-type">repository type</a>. In this example, we’ll create a local directory on the backup server (also known as the repository host, <code class="language-plaintext highlighter-rouge">repo1</code>):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-u</span> pgbackrest <span class="nb">mkdir</span> /shared/repo1
</code></pre></div></div>

<p><em>Remark:</em> You can reuse the previous repository (where you already have archives and backups) if you don’t want to start from scratch. Just ensure it’s accessible from the <code class="language-plaintext highlighter-rouge">repo1</code> host and that the <code class="language-plaintext highlighter-rouge">pgbackrest</code> user has full access to it.</p>

<h2 id="configuration">Configuration</h2>

<p>Now, let’s move on to configuring pgBackRest on <code class="language-plaintext highlighter-rouge">repo1</code>:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[global]</span>
<span class="py">repo1-path</span><span class="p">=</span><span class="s">/shared/repo1</span>
<span class="py">repo1-retention-full</span><span class="p">=</span><span class="s">4</span>
<span class="py">repo1-bundle</span><span class="p">=</span><span class="s">y</span>
<span class="py">repo1-block</span><span class="p">=</span><span class="s">y</span>
<span class="py">start-fast</span><span class="p">=</span><span class="s">y</span>
<span class="py">log-level-console</span><span class="p">=</span><span class="s">info</span>
<span class="py">log-level-file</span><span class="p">=</span><span class="s">detail</span>
<span class="py">delta</span><span class="p">=</span><span class="s">y</span>
<span class="py">process-max</span><span class="p">=</span><span class="s">2</span>
<span class="py">compress-type</span><span class="p">=</span><span class="s">zst</span>
<span class="py">backup-standby</span><span class="p">=</span><span class="s">y</span>

<span class="nn">[mycluster]</span>
<span class="py">pg1-path</span><span class="p">=</span><span class="s">/var/lib/pgsql/16/data</span>
<span class="py">pg1-host</span><span class="p">=</span><span class="s">pg1</span>
<span class="py">pg1-host-user</span><span class="p">=</span><span class="s">postgres</span>
<span class="py">pg2-path</span><span class="p">=</span><span class="s">/var/lib/pgsql/16/data</span>
<span class="py">pg2-host</span><span class="p">=</span><span class="s">pg2</span>
<span class="py">pg2-host-user</span><span class="p">=</span><span class="s">postgres</span>
</code></pre></div></div>

<p>The global section here includes options primarily used to control the behavior of the backup command and specify where to store the backups and archives. The <code class="language-plaintext highlighter-rouge">[mycluster]</code> stanza section, on the other hand, defines the locations of all the PostgreSQL nodes within the cluster.</p>

<p>Speaking about the PostgreSQL nodes, let’s now update the pgBackRest configuration on <code class="language-plaintext highlighter-rouge">pg1</code> and <code class="language-plaintext highlighter-rouge">pg2</code>:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[global]</span>
<span class="py">repo1-host</span><span class="p">=</span><span class="s">repo1</span>
<span class="py">repo1-host-user</span><span class="p">=</span><span class="s">pgbackrest</span>
<span class="py">log-level-console</span><span class="p">=</span><span class="s">info</span>
<span class="py">log-level-file</span><span class="p">=</span><span class="s">detail</span>
<span class="py">compress-type</span><span class="p">=</span><span class="s">zst</span>

<span class="nn">[mycluster]</span>
<span class="py">pg1-path</span><span class="p">=</span><span class="s">/var/lib/pgsql/16/data</span>
</code></pre></div></div>

<p>Since the backup command will be executed on <code class="language-plaintext highlighter-rouge">repo1</code>, the key point is to define the location of the locally running database in the <code class="language-plaintext highlighter-rouge">[mycluster]</code> stanza section and configure how to reach the repository host in the global section. Additionally, options for controlling archiving (<code class="language-plaintext highlighter-rouge">archive-push</code>/<code class="language-plaintext highlighter-rouge">archive-get</code>) and restore commands should also be specified here.</p>

<p>Next, initialize the backup repository by running the <code class="language-plaintext highlighter-rouge">stanza-create</code> command on the repository host (<code class="language-plaintext highlighter-rouge">repo1</code>). After that, run the <code class="language-plaintext highlighter-rouge">check</code> command to ensure that the archiving process is working correctly and that WAL archives are successfully being pushed to the new repository:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> pgbackrest pgbackrest <span class="nt">--stanza</span><span class="o">=</span>mycluster stanza-create
INFO: stanza-create <span class="nb">command </span>begin 2.53.1: ...
INFO: stanza-create <span class="k">for </span>stanza <span class="s1">'mycluster'</span> on repo1
INFO: stanza-create <span class="nb">command </span>end: completed successfully

<span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> pgbackrest pgbackrest <span class="nt">--stanza</span><span class="o">=</span>mycluster check
INFO: check <span class="nb">command </span>begin 2.53.1: ...
INFO: check repo1 <span class="o">(</span>standby<span class="o">)</span>
INFO: switch wal not performed because this is a standby
INFO: check repo1 configuration <span class="o">(</span>primary<span class="o">)</span>
INFO: check repo1 archive <span class="k">for </span>WAL <span class="o">(</span>primary<span class="o">)</span>
INFO: WAL segment 000000010000000000000057 successfully archived to <span class="s1">'...'</span> on repo1
INFO: check <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>You can also run the <code class="language-plaintext highlighter-rouge">check</code> command on the primary (it won’t have any effect on the standby server):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest <span class="nt">--stanza</span><span class="o">=</span>mycluster check
</code></pre></div></div>

<p><em>Remark:</em> If you chose to reuse the existing repository, there’s no need to re-run the <code class="language-plaintext highlighter-rouge">stanza-create</code> command, as the repository is already initialized. However, you can still run it, and the <code class="language-plaintext highlighter-rouge">stanza-create</code> command will simply verify that everything is fine:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> pgbackrest pgbackrest <span class="nt">--stanza</span><span class="o">=</span>mycluster stanza-create
INFO: stanza-create <span class="nb">command </span>begin 2.53.1: ...
INFO: stanza-create <span class="k">for </span>stanza <span class="s1">'mycluster'</span> on repo1
INFO: stanza <span class="s1">'mycluster'</span> already exists on repo1 and is valid
INFO: stanza-create <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<h2 id="backups">Backups</h2>

<p>Let’s now take a backup from the repository host (<code class="language-plaintext highlighter-rouge">repo1</code>):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> pgbackrest pgbackrest backup <span class="nt">--stanza</span><span class="o">=</span>mycluster <span class="nt">--type</span><span class="o">=</span>full
INFO: backup <span class="nb">command </span>begin 2.53.1: ...
INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
INFO: backup start archive <span class="o">=</span> 000000010000000000000059, lsn <span class="o">=</span> 0/59000028
INFO: <span class="nb">wait </span><span class="k">for </span>replay on the standby to reach 0/59000028
INFO: replay on the standby reached 0/59000028
INFO: check archive <span class="k">for </span>prior segment 000000010000000000000058
INFO: execute non-exclusive backup stop and <span class="nb">wait </span><span class="k">for </span>all WAL segments to archive
INFO: backup stop archive <span class="o">=</span> 000000010000000000000059, lsn <span class="o">=</span> 0/59000138
INFO: check archive <span class="k">for </span>segment<span class="o">(</span>s<span class="o">)</span> 000000010000000000000059:000000010000000000000059
INFO: new backup label <span class="o">=</span> 20240923-142704F
INFO: full backup size <span class="o">=</span> 1.5GB, file total <span class="o">=</span> 1281
INFO: backup <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>What happens if the standby server is down?</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> pgbackrest pgbackrest backup <span class="nt">--stanza</span><span class="o">=</span>mycluster <span class="nt">--type</span><span class="o">=</span>full
INFO: backup <span class="nb">command </span>begin 2.53.1: ...
WARN: unable to check pg2: <span class="o">[</span>DbConnectError] raised from remote-0 ssh protocol on <span class="s1">'pg2'</span>:
    unable to connect to <span class="s1">'dbname='</span>postgres<span class="s1">' port=5432'</span>:
        connection to server on socket <span class="s2">"/run/postgresql/.s.PGSQL.5432"</span> failed: No such file or directory
    Is the server running locally and accepting connections on that socket?
ERROR: <span class="o">[</span>056]: unable to find standby cluster - cannot proceed
INFO: backup <span class="nb">command </span>end: aborted with exception <span class="o">[</span>056]
</code></pre></div></div>

<p>If the standby server is down and the configuration is set to take backups from the standby (<code class="language-plaintext highlighter-rouge">backup-standby=y</code>), the backup command will fail. In such cases, you can trigger a backup from the primary by using the <code class="language-plaintext highlighter-rouge">--no-backup-standby</code> option in the command line:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> pgbackrest pgbackrest backup <span class="nt">--stanza</span><span class="o">=</span>mycluster <span class="nt">--type</span><span class="o">=</span>full <span class="nt">--no-backup-standby</span>
INFO: backup <span class="nb">command </span>begin 2.53.1: ...
WARN: unable to check pg2: <span class="o">[</span>DbConnectError] raised from remote-0 ssh protocol on <span class="s1">'pg2'</span>:
    unable to connect to <span class="s1">'dbname='</span>postgres<span class="s1">' port=5432'</span>:
        connection to server on socket <span class="s2">"/run/postgresql/.s.PGSQL.5432"</span> failed: No such file or directory
    Is the server running locally and accepting connections on that socket?
INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
INFO: backup start archive <span class="o">=</span> 00000001000000000000005C, lsn <span class="o">=</span> 0/5C000028
INFO: check archive <span class="k">for </span>prior segment 00000001000000000000005B
INFO: execute non-exclusive backup stop and <span class="nb">wait </span><span class="k">for </span>all WAL segments to archive
INFO: backup stop archive <span class="o">=</span> 00000001000000000000005C, lsn <span class="o">=</span> 0/5C000100
INFO: check archive <span class="k">for </span>segment<span class="o">(</span>s<span class="o">)</span> 00000001000000000000005C:00000001000000000000005C
INFO: new backup label <span class="o">=</span> 20240923-143128F
INFO: full backup size <span class="o">=</span> 1.5GB, file total <span class="o">=</span> 1281
INFO: backup <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<h2 id="new-option-preview">New option preview</h2>

<p>To avoid adjusting the configuration or manually triggering backups, an upcoming pgBackRest release will introduce a new option: <a href="https://github.com/pgbackrest/pgbackrest/commit/a42629f87ab70ffaee8d1241eb50c5ea7154a87d"><code class="language-plaintext highlighter-rouge">backup-standby=prefer</code></a>. This option will allow backups to be taken from the standby if available, or automatically fall back to the primary if the standby is down.</p>

<p>To test this new feature, I’ve installed the development version of pgBackRest on all nodes:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pgbackrest version
pgBackRest 2.54dev
</code></pre></div></div>

<p>What would happen if I only upgraded the pgBackRest version on the repository host (<code class="language-plaintext highlighter-rouge">repo1</code>) but not on the PostgreSQL nodes (<code class="language-plaintext highlighter-rouge">pg1</code> and <code class="language-plaintext highlighter-rouge">pg2</code>)?</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-u</span> pgbackrest pgbackrest backup <span class="nt">--stanza</span><span class="o">=</span>mycluster <span class="nt">--type</span><span class="o">=</span>full <span class="nt">--no-backup-standby</span>
INFO: backup <span class="nb">command </span>begin 2.54dev: ...
WARN: unable to check pg1: <span class="o">[</span>ProtocolError] expected value <span class="s1">'2.54dev'</span> <span class="k">for </span>greeting key <span class="s1">'version'</span> but got <span class="s1">'2.53.1'</span>
      HINT: is the same version of pgBackRest installed on the <span class="nb">local </span>and remote host?
WARN: unable to check pg2: <span class="o">[</span>ProtocolError] expected value <span class="s1">'2.54dev'</span> <span class="k">for </span>greeting key <span class="s1">'version'</span> but got <span class="s1">'2.53.1'</span>
      HINT: is the same version of pgBackRest installed on the <span class="nb">local </span>and remote host?
ERROR: <span class="o">[</span>056]: unable to find primary cluster - cannot proceed
      HINT: are all available clusters <span class="k">in </span>recovery?
INFO: backup <span class="nb">command </span>end: aborted with exception <span class="o">[</span>056]
</code></pre></div></div>

<p>That’s probably the main drawback about using repository hosts: the pgBackRest version installed on the repository host must exactly match the version installed on the PostgreSQL hosts.</p>

<p>Finally, let’s test the new option:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-u</span> pgbackrest pgbackrest backup <span class="nt">--stanza</span><span class="o">=</span>mycluster <span class="nt">--type</span><span class="o">=</span>full <span class="nt">--backup-standby</span><span class="o">=</span>prefer
INFO: backup <span class="nb">command </span>begin 2.54dev: ...
WARN: unable to check pg2: <span class="o">[</span>DbConnectError] raised from remote-0 ssh protocol on <span class="s1">'pg2'</span>:
    unable to connect to <span class="s1">'dbname='</span>postgres<span class="s1">' port=5432'</span>:
        connection to server on socket <span class="s2">"/run/postgresql/.s.PGSQL.5432"</span> failed: No such file or directory
    Is the server running locally and accepting connections on that socket?
WARN: unable to find a standby to perform the backup, using primary instead
INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
INFO: backup start archive <span class="o">=</span> 00000001000000000000005E, lsn <span class="o">=</span> 0/5E000028
INFO: check archive <span class="k">for </span>prior segment 00000001000000000000005D
INFO: execute non-exclusive backup stop and <span class="nb">wait </span><span class="k">for </span>all WAL segments to archive
INFO: backup stop archive <span class="o">=</span> 00000001000000000000005E, lsn <span class="o">=</span> 0/5E000138
INFO: check archive <span class="k">for </span>segment<span class="o">(</span>s<span class="o">)</span> 00000001000000000000005E:00000001000000000000005E
INFO: new backup label <span class="o">=</span> 20240923-144946F
INFO: full backup size <span class="o">=</span> 1.5GB, file total <span class="o">=</span> 1281
INFO: backup <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<hr />

<h1 id="conclusion">Conclusion</h1>

<p>Setting up a dedicated pgBackRest repository host makes managing backups easier and reduces the load on your PostgreSQL nodes, especially as your cluster grows and you add more standby servers or automated failover tools.</p>

<p>Whether you’re reusing an existing repository or starting fresh, the setup process is really straightforward. Plus, with features like <code class="language-plaintext highlighter-rouge">backup-standby=prefer</code> coming in the next release, backups will be more flexible, allowing for automatic fallback to the primary when the standby is down. PgBackRest keeps getting better with each new version, so keep an eye on the release radar (and release notes) to stay updated on the latest features and improvements!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[As I mentioned in my last blog post, as your cluster grows with multiple standby servers and potentially automated failover (using tools like Patroni), it becomes more practical to set up a dedicated repository host, also known as a dedicated backup server. This backup server can then trigger backups and automatically select the appropriate node in case of failover, eliminating the need for manual intervention. In this post, I’ll show you how easy it is to add a repository host to an existing cluster. I’ll also give you a sneak peek at a new feature expected to be included in the next pgBackRest release 😉]]></summary></entry><entry><title type="html">pgBackRest backups from the standby server</title><link href="https://pgstef.github.io/2024/09/23/pgbackrest_backups_from_the_standby_server.html" rel="alternate" type="text/html" title="pgBackRest backups from the standby server" /><published>2024-09-23T10:15:00+02:00</published><updated>2024-09-23T10:15:00+02:00</updated><id>https://pgstef.github.io/2024/09/23/pgbackrest_backups_from_the_standby_server</id><content type="html" xml:base="https://pgstef.github.io/2024/09/23/pgbackrest_backups_from_the_standby_server.html"><![CDATA[<p>Recently, we’ve received many questions about how to take backups from a standby server using pgBackRest. In this post, I’d like to clarify one of the most frequently asked questions and address a common misconception for new users.</p>

<p>First of all, it’s important to understand that taking a backup exclusively from the standby server is not currently possible. When you trigger a backup from the standby, pgBackRest creates a standby backup that is identical to a backup performed on the primary. It does this by starting/stopping the backup on the primary, copying only files that are replicated from the standby, then copying the remaining few files from the primary.</p>

<p>For this setup to work, both the primary and standby servers must share a common backup repository. This can be any supported <a href="https://pgbackrest.org/configuration.html#section-repository/option-repo-type">repository type</a>.</p>

<p>Let’s take an example, using an NFS mount point.</p>

<!--MORE-->

<hr />

<h1 id="example-setup-for-backups-from-the-standby-server">Example setup for backups from the standby server</h1>

<h2 id="initial-situation">Initial situation</h2>

<p>Both the primary (<code class="language-plaintext highlighter-rouge">pg1</code>) and the standby (<code class="language-plaintext highlighter-rouge">pg2</code>) are seeing the same content of the mentioned NFS mount:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>postgres@pg1 ~]<span class="nv">$ </span><span class="nb">ls</span> /shared/
<span class="o">[</span>postgres@pg1 ~]<span class="nv">$ </span><span class="nb">touch</span> /shared/test_write_from the primary
<span class="o">[</span>postgres@pg1 ~]<span class="nv">$ </span><span class="nb">mkdir</span> /shared/pgbackrest
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>postgres@pg2 ~]<span class="nv">$ </span><span class="nb">ls</span> /shared
pgbackrest  test_write_from
</code></pre></div></div>

<p>And we’ve got a working replication connection:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">postgres</span><span class="o">=</span><span class="c"># SELECT * FROM pg_stat_replication;</span>
-[ RECORD 1 <span class="o">]</span><span class="nt">----</span>+------------------------------
pid              | 27773
usename          | replicator
application_name | pg2
state            | streaming
sent_lsn         | 0/500AD6C8
write_lsn        | 0/500AD6C8
flush_lsn        | 0/500AD6C8
replay_lsn       | 0/500AD6C8
sync_state       | async
</code></pre></div></div>

<h2 id="wal-archiving">WAL archiving</h2>

<p>Let’s configure	pgBackRest on <code class="language-plaintext highlighter-rouge">pg1</code> and <code class="language-plaintext highlighter-rouge">pg2</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pgbackrest version
pgBackRest 2.53.1

<span class="nv">$ </span><span class="nb">cat</span><span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh"> | sudo tee "/etc/pgbackrest.conf"
[global]
repo1-path=/shared/pgbackrest
repo1-retention-full=4
repo1-bundle=y
repo1-block=y
start-fast=y
log-level-console=info
log-level-file=detail
delta=y
process-max=2
compress-type=zst

[mycluster]
pg1-path=/var/lib/pgsql/16/data
</span><span class="no">EOF
</span></code></pre></div></div>

<p>Enable archiving on the primary (<code class="language-plaintext highlighter-rouge">pg1</code>):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat</span><span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh"> | sudo tee -a "/var/lib/pgsql/16/data/postgresql.auto.conf"
archive_mode = on
archive_command = 'pgbackrest --stanza=mycluster archive-push %p'
</span><span class="no">EOF

</span><span class="nv">$ </span><span class="nb">sudo </span>systemctl restart postgresql-16
</code></pre></div></div>

<p>Initialize the backup repository content running the <code class="language-plaintext highlighter-rouge">stanza-create</code> command on the primary (<code class="language-plaintext highlighter-rouge">pg1</code>):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest <span class="nt">--stanza</span><span class="o">=</span>mycluster stanza-create
INFO: stanza-create <span class="nb">command </span>begin 2.53.1: ...
INFO: stanza-create <span class="k">for </span>stanza <span class="s1">'mycluster'</span> on repo1
INFO: stanza-create <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>Since both the primary and the standby are sharing a common backup repository, you just need to run the above command only once!</p>

<p>Now, let’s check that the archiving system works correctly by running the <code class="language-plaintext highlighter-rouge">check</code> command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest <span class="nt">--stanza</span><span class="o">=</span>mycluster check
INFO: check <span class="nb">command </span>begin 2.53.1: ...
INFO: check repo1 configuration <span class="o">(</span>primary<span class="o">)</span>
INFO: check repo1 archive <span class="k">for </span>WAL <span class="o">(</span>primary<span class="o">)</span>
INFO: WAL segment 000000010000000000000050 successfully archived to <span class="s1">'...'</span> on repo1
INFO: check <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>This will trigger a new archive on the primary and then check if pgBackRest, running from the server where the command is executed, can locate that archived file in the backup repository. It’s helpful to run this <code class="language-plaintext highlighter-rouge">check</code> command on both the primary and the standby servers to confirm that the archiving system is functioning correctly and that both servers can access the same backup repository. But we’ll do that later on the standby since we still need to adjust the pgBackRest configuration.</p>

<p>When using <em>Streaming Replication</em>, it’s generally recommended to set up replication slots. This ensures that the primary doesn’t remove WAL files needed by the standby, or rows that could cause a recovery conflict, even when the standby is disconnected. Since the primary archives those WAL segments before recycling them, and both nodes share a common backup repository, you can rely on those archives to help the standby (<code class="language-plaintext highlighter-rouge">pg2</code>) catch up. To do this, set the <code class="language-plaintext highlighter-rouge">restore_command</code> on the standby:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat</span><span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh"> | sudo tee -a "/var/lib/pgsql/16/data/postgresql.auto.conf"
restore_command = 'pgbackrest --stanza=mycluster archive-get %f "%p"'
</span><span class="no">EOF

</span><span class="nv">$ </span><span class="nb">sudo </span>systemctl reload postgresql-16
</code></pre></div></div>

<p>To verify that replication and WAL archiving are working, you can rely on <code class="language-plaintext highlighter-rouge">pg_stat_replication</code> and <code class="language-plaintext highlighter-rouge">pg_stat_archiver</code>:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">postgres</span><span class="o">=#</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">pg_stat_replication</span><span class="p">;</span>
<span class="o">-</span><span class="p">[</span> <span class="n">RECORD</span> <span class="mi">1</span> <span class="p">]</span><span class="c1">----+------------------------------</span>
<span class="n">pid</span>              <span class="o">|</span> <span class="mi">28865</span>
<span class="n">usename</span>          <span class="o">|</span> <span class="n">replicator</span>
<span class="n">application_name</span> <span class="o">|</span> <span class="n">pg2</span>
<span class="k">state</span>            <span class="o">|</span> <span class="n">streaming</span>
<span class="n">sent_lsn</span>         <span class="o">|</span> <span class="mi">0</span><span class="o">/</span><span class="mi">520000</span><span class="n">D8</span>
<span class="n">write_lsn</span>        <span class="o">|</span> <span class="mi">0</span><span class="o">/</span><span class="mi">520000</span><span class="n">D8</span>
<span class="n">flush_lsn</span>        <span class="o">|</span> <span class="mi">0</span><span class="o">/</span><span class="mi">520000</span><span class="n">D8</span>
<span class="n">replay_lsn</span>       <span class="o">|</span> <span class="mi">0</span><span class="o">/</span><span class="mi">520000</span><span class="n">D8</span>
<span class="n">sync_state</span>       <span class="o">|</span> <span class="n">async</span>
<span class="p">...</span>

<span class="n">postgres</span><span class="o">=#</span> <span class="k">SELECT</span>
            <span class="n">pg_walfile_name</span><span class="p">(</span><span class="n">pg_current_wal_lsn</span><span class="p">()),</span>
            <span class="n">last_archived_wal</span> <span class="k">FROM</span> <span class="n">pg_stat_archiver</span><span class="p">;</span>
<span class="o">-</span><span class="p">[</span> <span class="n">RECORD</span> <span class="mi">1</span> <span class="p">]</span><span class="c1">------+------------------------------</span>
<span class="n">pg_walfile_name</span>    <span class="o">|</span> <span class="mi">000000010000000000000052</span>
<span class="n">last_archived_wal</span>  <span class="o">|</span> <span class="mi">000000010000000000000051</span>
</code></pre></div></div>

<p>You can also use the pgBackRest <code class="language-plaintext highlighter-rouge">info</code> command to look at what’s inside the repository:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest info <span class="nt">--stanza</span><span class="o">=</span>mycluster
...
db <span class="o">(</span>current<span class="o">)</span>
    wal archive min/max <span class="o">(</span>16<span class="o">)</span>: 000000010000000000000050/000000010000000000000051
</code></pre></div></div>

<h2 id="ssh-access">SSH access</h2>

<p>Now that replication and WAL archiving are working, and we’ve successfully verified that the standby server can access the shared backup repository, we can prepare the servers to allow backups to be taken from the standby. To achieve this, the pgBackRest process on the standby will need to trigger a local process on the primary server to establish a database connection via Unix sockets. This ensures that pgBackRest can interact with the primary for certain operations, even when the backup is initiated from the standby. For the communication between the servers, pgBackRest can be configured to use passwordless SSH.</p>

<p>The SSH setup is up to you, but usually it is as simple as creating SSH keys and authorize them on the other node. Example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-u</span> postgres ssh-keygen <span class="nt">-f</span> /var/lib/pgsql/.ssh/id_rsa <span class="nt">-t</span> rsa <span class="nt">-b</span> 4096 <span class="nt">-N</span> <span class="s2">""</span>
</code></pre></div></div>

<p>Then, copy the generated public keys to the other node:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># From pg1</span>
<span class="o">[</span>postgres@pg1 ~]<span class="nv">$ </span>ssh-copy-id postgres@pg2
<span class="o">[</span>postgres@pg1 ~]<span class="nv">$ </span>ssh postgres@pg2 <span class="nb">hostname</span>

<span class="c"># From pg2</span>
<span class="o">[</span>postgres@pg2 ~]<span class="nv">$ </span>ssh-copy-id postgres@pg1
<span class="o">[</span>postgres@pg2 ~]<span class="nv">$ </span>ssh postgres@pg1 <span class="nb">hostname</span>
</code></pre></div></div>

<p>Finally now, we’ll need to adjust the pgBackRest configuration to add a connection to the primary (<code class="language-plaintext highlighter-rouge">pg2-path</code>, <code class="language-plaintext highlighter-rouge">pg2-host</code>, <code class="language-plaintext highlighter-rouge">pg2-host-user</code>) and specify that we want to take a backup from the standby server (<code class="language-plaintext highlighter-rouge">backup-standby=y</code>). The configuration on the standby should then look like this:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[global]</span>
<span class="py">repo1-path</span><span class="p">=</span><span class="s">/shared/pgbackrest</span>
<span class="py">repo1-retention-full</span><span class="p">=</span><span class="s">4</span>
<span class="py">repo1-bundle</span><span class="p">=</span><span class="s">y</span>
<span class="py">repo1-block</span><span class="p">=</span><span class="s">y</span>
<span class="py">start-fast</span><span class="p">=</span><span class="s">y</span>
<span class="py">log-level-console</span><span class="p">=</span><span class="s">info</span>
<span class="py">log-level-file</span><span class="p">=</span><span class="s">detail</span>
<span class="py">delta</span><span class="p">=</span><span class="s">y</span>
<span class="py">process-max</span><span class="p">=</span><span class="s">2</span>
<span class="py">compress-type</span><span class="p">=</span><span class="s">zst</span>
<span class="py">backup-standby</span><span class="p">=</span><span class="s">y</span>

<span class="nn">[mycluster]</span>
<span class="py">pg1-path</span><span class="p">=</span><span class="s">/var/lib/pgsql/16/data</span>
<span class="py">pg2-path</span><span class="p">=</span><span class="s">/var/lib/pgsql/16/data</span>
<span class="py">pg2-host</span><span class="p">=</span><span class="s">pg1</span>
<span class="py">pg2-host-user</span><span class="p">=</span><span class="s">postgres</span>
</code></pre></div></div>

<p>As I mentioned above, it is helpful to run the <code class="language-plaintext highlighter-rouge">check</code> command on the standby server once we’ve adjusted the configuration before taking a backup:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest <span class="nt">--stanza</span><span class="o">=</span>mycluster check
INFO: check <span class="nb">command </span>begin 2.53.1: ...
INFO: check repo1 <span class="o">(</span>standby<span class="o">)</span>
INFO: switch wal not performed because this is a standby
INFO: check repo1 configuration <span class="o">(</span>primary<span class="o">)</span>
INFO: check repo1 archive <span class="k">for </span>WAL <span class="o">(</span>primary<span class="o">)</span>
INFO: WAL segment 000000010000000000000053 successfully archived to <span class="s1">'...'</span> on repo1
INFO: check <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<h2 id="backups">Backups</h2>

<p>Now, take a full backup:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest backup <span class="nt">--stanza</span><span class="o">=</span>mycluster <span class="nt">--type</span><span class="o">=</span>full
INFO: backup <span class="nb">command </span>begin 2.53.1: ...
INFO: execute non-exclusive backup start:
        backup begins after the requested immediate checkpoint completes
INFO: backup start archive <span class="o">=</span> 000000010000000000000055, lsn <span class="o">=</span> 0/55000028
INFO: <span class="nb">wait </span><span class="k">for </span>replay on the standby to reach 0/55000028
INFO: replay on the standby reached 0/55000028
INFO: check archive <span class="k">for </span>prior segment 000000010000000000000054
INFO: execute non-exclusive backup stop and <span class="nb">wait </span><span class="k">for </span>all WAL segments to archive
INFO: backup stop archive <span class="o">=</span> 000000010000000000000055, lsn <span class="o">=</span> 0/55000138
INFO: check archive <span class="k">for </span>segment<span class="o">(</span>s<span class="o">)</span> 000000010000000000000055:000000010000000000000055
INFO: new backup label <span class="o">=</span> 20240920-144928F
2INFO: full backup size <span class="o">=</span> 1.5GB, file total <span class="o">=</span> 1280
INFO: backup <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">wait for replay on the standby to reach 0/55000028</code>: pgBackRest ensures that the standby server has fully caught up with the primary before allowing the backup to proceed. This prevents backing up a lagging standby.</li>
  <li><code class="language-plaintext highlighter-rouge">check archive for prior segment 000000010000000000000054</code>: pgBackRest checks if the previous WAL archive has been successfully pushed to the repository by the primary within the <code class="language-plaintext highlighter-rouge">archive-timeout</code> period. If this check fails, it could indicate that the standby server lacks access to the shared repository, or that the archiving process on the primary is too slow.</li>
</ul>

<p>If the archiving process on the primary is too slow, you might consider enabling <a href="https://pgbackrest.org/user-guide.html#async-archiving">asynchronous archiving</a>. The pgBackRest user guide highlights several <a href="https://pgbackrest.org/user-guide.html#quickstart/performance-tuning">performance tuning</a> settings that can help resolve this issue: <code class="language-plaintext highlighter-rouge">archive-async</code>, <code class="language-plaintext highlighter-rouge">process-max</code>, and <code class="language-plaintext highlighter-rouge">compress-type=zst</code> are the recommended options to improve archiving speed.</p>

<p>Finally, look at the <code class="language-plaintext highlighter-rouge">info</code> command to know what’s inside the repository:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest info <span class="nt">--stanza</span><span class="o">=</span>mycluster
stanza: mycluster
    status: ok
    cipher: none

    db <span class="o">(</span>current<span class="o">)</span>
        wal archive min/max <span class="o">(</span>16<span class="o">)</span>: 000000010000000000000050/000000010000000000000055

        full backup: 20240920-144928F
            timestamp start/stop: 2024-09-20 14:49:28+00 / 2024-09-20 14:49:32+00
            wal start/stop: 000000010000000000000055 / 000000010000000000000055
            database size: 1.5GB, database backup size: 1.5GB
            repo1: backup size: 67.9MB
</code></pre></div></div>

<hr />

<h1 id="conclusion">Conclusion</h1>

<p>Setting up backups from a standby server is an effective way to reduce the load on the primary server. While pgBackRest still relies on the primary server for certain tasks, using the standby for most of the backup process can greatly improve performance in a replicated environment. However, while the technical setup is relatively straightforward, the more challenging part of this architecture lies in deciding where, when, and how to trigger the backups.</p>

<p>In most cases, you would configure pgBackRest identically on both nodes and use a cron job to <a href="https://pgbackrest.org/user-guide.html#quickstart/schedule-backup">schedule the backups</a>. To avoid complications (and manual operations) when switching roles between the primary and standby, the easiest solution is to check the result of <code class="language-plaintext highlighter-rouge">pg_is_in_recovery()</code> in the cron job. This way, backups are automatically triggered on the correct node, regardless of which server is currently acting as the standby.</p>

<p>However, this approach is most efficient when you only have a single standby server. As your cluster grows, with multiple standbys and possibly automated failover (using tools like Patroni), it becomes more practical to set up a <a href="https://pgbackrest.org/user-guide.html#repo-host">dedicated repository host</a> (also known as a dedicated backup server). This backup server can then trigger the backups and automatically select the appropriate node.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Recently, we’ve received many questions about how to take backups from a standby server using pgBackRest. In this post, I’d like to clarify one of the most frequently asked questions and address a common misconception for new users. First of all, it’s important to understand that taking a backup exclusively from the standby server is not currently possible. When you trigger a backup from the standby, pgBackRest creates a standby backup that is identical to a backup performed on the primary. It does this by starting/stopping the backup on the primary, copying only files that are replicated from the standby, then copying the remaining few files from the primary. For this setup to work, both the primary and standby servers must share a common backup repository. This can be any supported repository type. Let’s take an example, using an NFS mount point.]]></summary></entry><entry><title type="html">In-memory disk for PostgreSQL temporary files</title><link href="https://pgstef.github.io/2024/05/20/in_memory_tmp_files.html" rel="alternate" type="text/html" title="In-memory disk for PostgreSQL temporary files" /><published>2024-05-20T09:30:00+02:00</published><updated>2024-05-20T09:30:00+02:00</updated><id>https://pgstef.github.io/2024/05/20/in_memory_tmp_files</id><content type="html" xml:base="https://pgstef.github.io/2024/05/20/in_memory_tmp_files.html"><![CDATA[<p>Recently, while debugging a performance issue of a <code class="language-plaintext highlighter-rouge">CREATE INDEX</code> operation, I was reminded that PostgreSQL might produce temporary files when executing a parallel query, including parallel index creation, because each worker process has its own memory and might need to use disk space for sorting or hash tables.</p>

<p>Thanks to Peter Geoghegan answering <a href="https://www.postgresql.org/message-id/flat/13fa6c59-16bc-9945-f913-2e0c58d34d12@portavita.eu">this pgsql-admin</a> email thread.</p>

<p>So, in order to try to speed up that index creation, I thought it would be beneficial to move those temporary files directly into memory using a tmpfs and wanted to test that theory, writing this blog post :-)</p>

<!--MORE-->

<hr />

<h1 id="example">Example</h1>

<p>Let’s first enable logging of the temporary files to evaluate the change we’re planning to make:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ALTER</span> <span class="k">SYSTEM</span> <span class="k">SET</span> <span class="n">log_temp_files</span> <span class="k">TO</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">ALTER</span> <span class="k">SYSTEM</span> <span class="k">SET</span> <span class="n">log_min_duration_statement</span> <span class="k">TO</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="n">pg_reload_conf</span><span class="p">();</span>
</code></pre></div></div>

<p>Create a test database using <code class="language-plaintext highlighter-rouge">pgbench</code> and an index on the <code class="language-plaintext highlighter-rouge">pgbench_accounts</code> table:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>createdb bench
<span class="nv">$ </span>/usr/pgsql-16/bin/pgbench <span class="nt">-i</span> <span class="nt">-s</span> 100 bench
<span class="nv">$ </span>psql bench <span class="nt">-c</span> <span class="s2">"CREATE INDEX ON pgbench_accounts (aid, filler);"</span>
</code></pre></div></div>

<p>We can see from the logs the time it took to build the index (~5.9s) with temporary files involved:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LOG:  temporary file: path "base/pgsql_tmp/pgsql_tmp28501.0.fileset/0.0", size 541376512
STATEMENT:  CREATE INDEX ON pgbench_accounts (aid, filler);
LOG:  temporary file: path "base/pgsql_tmp/pgsql_tmp28501.0.fileset/1.0", size 541024256
STATEMENT:  CREATE INDEX ON pgbench_accounts (aid, filler);
LOG:  duration: 5936.468 ms  statement: CREATE INDEX ON pgbench_accounts (aid, filler);
</code></pre></div></div>

<p>Let’s try to re-create the index with a higher <code class="language-plaintext highlighter-rouge">maintenance_work_mem</code> to get rid of the temporary files:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DROP</span> <span class="k">INDEX</span> <span class="n">pgbench_accounts_aid_filler_idx</span><span class="p">;</span>
<span class="k">SET</span> <span class="n">maintenance_work_mem</span> <span class="k">TO</span> <span class="s1">'2GB'</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="k">INDEX</span> <span class="k">ON</span> <span class="n">pgbench_accounts</span> <span class="p">(</span><span class="n">aid</span><span class="p">,</span> <span class="n">filler</span><span class="p">);</span>
</code></pre></div></div>

<p>But the temporary files are not gone, as we expected given the comment on the pgsql-admin thread mentioned above.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LOG:  temporary file: path "base/pgsql_tmp/pgsql_tmp28501.10.fileset/0.0", size 365936640
STATEMENT:  CREATE INDEX ON pgbench_accounts (aid, filler);
LOG:  temporary file: path "base/pgsql_tmp/pgsql_tmp28501.10.fileset/2.0", size 354754560
STATEMENT:  CREATE INDEX ON pgbench_accounts (aid, filler);
LOG:  temporary file: path "base/pgsql_tmp/pgsql_tmp28501.10.fileset/1.0", size 361439232
STATEMENT:  CREATE INDEX ON pgbench_accounts (aid, filler);
LOG:  duration: 4541.701 ms  statement: CREATE INDEX ON pgbench_accounts (aid, filler);
</code></pre></div></div>

<p>So, let’s disable parallel query execution to check:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DROP</span> <span class="k">INDEX</span> <span class="n">pgbench_accounts_aid_filler_idx</span><span class="p">;</span>
<span class="k">SET</span> <span class="n">maintenance_work_mem</span> <span class="k">TO</span> <span class="s1">'2GB'</span><span class="p">;</span>
<span class="k">SET</span> <span class="n">max_parallel_workers</span> <span class="k">TO</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="k">INDEX</span> <span class="k">ON</span> <span class="n">pgbench_accounts</span> <span class="p">(</span><span class="n">aid</span><span class="p">,</span> <span class="n">filler</span><span class="p">);</span>
</code></pre></div></div>

<p>And voilà! The temporary files are gone:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LOG:  duration: 4348.098 ms  statement: CREATE INDEX ON pgbench_accounts (aid, filler);
</code></pre></div></div>

<p>We got approximately the same creation time ~4.5s vs ~4.3s.
But what could we do to use both memory and parallel execution? Moving those temporary files into memory with tmpfs directories!</p>

<h2 id="configure-postgresql-to-use-tmpfs-directory">Configure PostgreSQL to use tmpfs directory</h2>

<p>To move temporary files to memory, we’ll need to use the <code class="language-plaintext highlighter-rouge">temp_tablespaces</code> setting which requires to create a tablespace.
So, we’ll first create that tablespace and then move it to a tmpfs location.</p>

<p>Let’s start by creating a root directory for our tablespace:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo mkdir</span> /var/pgsql_tmp
</code></pre></div></div>

<p>Now, we have to create a tablespace for those PostgreSQL temporary files:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="n">TABLESPACE</span> <span class="n">tbstmp</span> <span class="k">location</span> <span class="s1">'/var/pgsql_tmp'</span><span class="p">;</span>
</code></pre></div></div>

<p>PostgreSQL will create a sub-directory in <code class="language-plaintext highlighter-rouge">/var/pgsql_tmp</code> and we’ll need to make that one permanent if we don’t want to re-create the tablespace after each and every reboot:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">ls</span> /var/pgsql_tmp/
PG_16_202307071
</code></pre></div></div>

<p>Finally, to make the tmpfs mount persistent, add it to <code class="language-plaintext highlighter-rouge">/etc/fstab</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tmpfs   /var/pgsql_tmp/PG_16_202307071  tmpfs   rw,size=2G,uid=postgres,gid=postgres    0 0
</code></pre></div></div>

<p>In this example, <code class="language-plaintext highlighter-rouge">size=2G</code> configures the tmpfs instance to use up to 2GB of RAM.</p>

<p>After configuring <code class="language-plaintext highlighter-rouge">/etc/fstab</code>, reload systemd and mount the tmpfs instance:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl daemon-reload
<span class="nb">sudo </span>mount /var/pgsql_tmp/PG_16_202307071
</code></pre></div></div>

<p>Back to our initial example, let’s now try to set <code class="language-plaintext highlighter-rouge">temp_tablespaces</code> and create the index again:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DROP</span> <span class="k">INDEX</span> <span class="n">pgbench_accounts_aid_filler_idx</span><span class="p">;</span>
<span class="k">SET</span> <span class="n">maintenance_work_mem</span> <span class="k">TO</span> <span class="s1">'2GB'</span><span class="p">;</span>
<span class="k">RESET</span> <span class="n">max_parallel_workers</span><span class="p">;</span>
<span class="k">SET</span> <span class="n">temp_tablespaces</span> <span class="k">TO</span> <span class="s1">'tbstmp'</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="k">INDEX</span> <span class="k">ON</span> <span class="n">pgbench_accounts</span> <span class="p">(</span><span class="n">aid</span><span class="p">,</span> <span class="n">filler</span><span class="p">);</span>
</code></pre></div></div>

<p>The temporary files are using the in-memory tmpfs directory, which indeed speeds up the index creation (to ~3.9s):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LOG:  temporary file: path "pg_tblspc/16448/PG_16_202307071/pgsql_tmp/pgsql_tmp28501.11.fileset/1.0", size 361865216
STATEMENT:  CREATE INDEX ON pgbench_accounts (aid, filler);
LOG:  temporary file: path "pg_tblspc/16448/PG_16_202307071/pgsql_tmp/pgsql_tmp28501.11.fileset/0.0", size 364134400
STATEMENT:  CREATE INDEX ON pgbench_accounts (aid, filler);
LOG:  temporary file: path "pg_tblspc/16448/PG_16_202307071/pgsql_tmp/pgsql_tmp28501.11.fileset/2.0", size 356122624
STATEMENT:  CREATE INDEX ON pgbench_accounts (aid, filler);
LOG:  duration: 3977.606 ms  statement: CREATE INDEX ON pgbench_accounts (aid, filler);
</code></pre></div></div>

<p>To make that change permanent for all the temporary files, change the <code class="language-plaintext highlighter-rouge">temp_tablespaces</code> setting system-wide:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ALTER</span> <span class="k">SYSTEM</span> <span class="k">SET</span> <span class="n">temp_tablespaces</span> <span class="k">TO</span> <span class="s1">'tbstmp'</span><span class="p">;</span>
</code></pre></div></div>

<p>Finally, reload the PostgreSQL configuration for the changes to take effect:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">postgres</span><span class="o">=#</span> <span class="k">SELECT</span> <span class="n">pg_reload_conf</span><span class="p">();</span>
 <span class="n">pg_reload_conf</span>
<span class="c1">----------------</span>
 <span class="n">t</span>
<span class="p">(</span><span class="mi">1</span> <span class="k">row</span><span class="p">)</span>

<span class="n">postgres</span><span class="o">=#</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">pg_settings</span> <span class="k">WHERE</span> <span class="n">name</span> <span class="o">=</span> <span class="s1">'temp_tablespaces'</span><span class="p">;</span>
<span class="o">-</span><span class="p">[</span> <span class="n">RECORD</span> <span class="mi">1</span> <span class="p">]</span><span class="c1">---+-------------------------------------------------------------------</span>
<span class="n">name</span>            <span class="o">|</span> <span class="n">temp_tablespaces</span>
<span class="n">setting</span>         <span class="o">|</span> <span class="n">tbstmp</span>
<span class="n">unit</span>            <span class="o">|</span>
<span class="n">category</span>        <span class="o">|</span> <span class="n">Client</span> <span class="k">Connection</span> <span class="k">Defaults</span> <span class="o">/</span> <span class="k">Statement</span> <span class="n">Behavior</span>
<span class="n">short_desc</span>      <span class="o">|</span> <span class="k">Sets</span> <span class="n">the</span> <span class="n">tablespace</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="k">to</span> <span class="n">use</span> <span class="k">for</span> <span class="k">temporary</span> <span class="n">tables</span> <span class="k">and</span> <span class="n">sort</span> <span class="n">files</span><span class="p">.</span>
<span class="n">extra_desc</span>      <span class="o">|</span>
<span class="n">context</span>         <span class="o">|</span> <span class="k">user</span>
<span class="n">vartype</span>         <span class="o">|</span> <span class="n">string</span>
<span class="k">source</span>          <span class="o">|</span> <span class="k">session</span>
<span class="n">min_val</span>         <span class="o">|</span>
<span class="n">max_val</span>         <span class="o">|</span>
<span class="n">enumvals</span>        <span class="o">|</span>
<span class="n">boot_val</span>        <span class="o">|</span>
<span class="n">reset_val</span>       <span class="o">|</span> <span class="nv">"tbstmp"</span>
<span class="n">sourcefile</span>      <span class="o">|</span>
<span class="n">sourceline</span>      <span class="o">|</span>
<span class="n">pending_restart</span> <span class="o">|</span> <span class="n">f</span>
</code></pre></div></div>

<hr />

<h1 id="conclusion">Conclusion</h1>

<p>By following these steps, you can configure a tmpfs in-memory directory for PostgreSQL temporary files, potentially improving performance for certain operations!
Obviously, you’ll need to adjust the tmpfs size according to your system’s capacity and requirements…</p>

<p>So, what do you think about this move?</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Recently, while debugging a performance issue of a CREATE INDEX operation, I was reminded that PostgreSQL might produce temporary files when executing a parallel query, including parallel index creation, because each worker process has its own memory and might need to use disk space for sorting or hash tables. Thanks to Peter Geoghegan answering this pgsql-admin email thread. So, in order to try to speed up that index creation, I thought it would be beneficial to move those temporary files directly into memory using a tmpfs and wanted to test that theory, writing this blog post :-)]]></summary></entry><entry><title type="html">pgBackRest differential vs incremental backup</title><link href="https://pgstef.github.io/2023/07/03/pgbackrest_differential_vs_incremental_backup.html" rel="alternate" type="text/html" title="pgBackRest differential vs incremental backup" /><published>2023-07-03T10:25:00+02:00</published><updated>2023-07-03T10:25:00+02:00</updated><id>https://pgstef.github.io/2023/07/03/pgbackrest_differential_vs_incremental_backup</id><content type="html" xml:base="https://pgstef.github.io/2023/07/03/pgbackrest_differential_vs_incremental_backup.html"><![CDATA[<p>One of the most frequent questions I get is to actually explain the difference between differential and incremental backups in pgBackRest.</p>

<p>As everyone might easily imagine, an incremental backup will only copy the database files (to keep it simple) that have been modified since the last successful backup.
If you have a full backup every Sunday, and then an incremental every other day, the backup from Wednesday will be based on the one from Tuesday.
That also means that if the backup from Monday or Tuesday is broken/corrupted (for any reason), you might not be able to recover from your Wednesday backup.</p>

<p>Now, what is a differential backup then? It is an incremental backup <em>but</em> it will be based on the last successful <strong>full</strong> backup.
So if you have a full backup every Sunday, and then a differential every other day, the backup from Wednesday will be based on the one from Sunday!
It would then doesn’t matter if the other differential backups get broken/corrupted, but since you’ll always compare what has been modified since Sunday, the size of the differential backups will be bigger over time.</p>

<p>Let’s have a concrete example.</p>

<!--MORE-->

<hr />

<h1 id="example">Example</h1>

<p>As you probably have understood now, incremental or differential changes the backup it refers to.</p>

<p>Let’s set up a database and take some incremental and differential backups:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Initialization</span>
<span class="nv">$ </span>/usr/pgsql-16/bin/pgbench <span class="nt">-i</span> <span class="nt">-s</span> 65

<span class="c"># Incremental backups</span>
<span class="nv">$ </span>pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo backup <span class="nt">--type</span><span class="o">=</span>full
<span class="nv">$ </span>/usr/pgsql-16/bin/pgbench <span class="nt">-n</span> <span class="nt">-b</span> simple-update <span class="nt">-t</span> 100
<span class="nv">$ </span>pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo backup <span class="nt">--type</span><span class="o">=</span>incr
<span class="nv">$ </span>/usr/pgsql-16/bin/pgbench <span class="nt">-n</span> <span class="nt">-b</span> simple-update <span class="nt">-t</span> 100
<span class="nv">$ </span>pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo backup <span class="nt">--type</span><span class="o">=</span>incr

<span class="c"># Differential backups</span>
<span class="nv">$ </span>pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo backup <span class="nt">--type</span><span class="o">=</span>full
<span class="nv">$ </span>/usr/pgsql-16/bin/pgbench <span class="nt">-n</span> <span class="nt">-b</span> simple-update <span class="nt">-t</span> 100
<span class="nv">$ </span>pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo backup <span class="nt">--type</span><span class="o">=</span>diff
<span class="nv">$ </span>/usr/pgsql-16/bin/pgbench <span class="nt">-n</span> <span class="nt">-b</span> simple-update <span class="nt">-t</span> 100
<span class="nv">$ </span>pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo backup <span class="nt">--type</span><span class="o">=</span>diff
</code></pre></div></div>

<p>Now, look at the repository content using the info command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo info

<span class="c"># Incremental</span>
full backup: 20230702-142534F
incr backup: 20230702-142534F_20230702-142548I
    backup reference list: 20230702-142534F
incr backup: 20230702-142534F_20230702-142601I
    backup reference list: 20230702-142534F, 20230702-142534F_20230702-142548I

<span class="c"># Differential</span>
full backup: 20230702-142626F
diff backup: 20230702-142626F_20230702-142639D
    backup reference list: 20230702-142626F
diff backup: 20230702-142626F_20230702-142652D
    backup reference list: 20230702-142626F
</code></pre></div></div>

<p>As I mentioned before, the second incremental backup <code class="language-plaintext highlighter-rouge">20230702-142534F_20230702-142601I</code> will contain the files that have been modified since the previous one.
To be able to restore it, we’d need to restore all the files that haven’t changed from the initial full backup <code class="language-plaintext highlighter-rouge">20230702-142534F</code> and also all the files modified before the first incremental
<code class="language-plaintext highlighter-rouge">20230702-142534F_20230702-142548I</code> that haven’t changed since then. That’s actually the information you get in the <code class="language-plaintext highlighter-rouge">backup reference list</code> section.</p>

<p>Let’s have a look at the backup manifest content:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># First incremental</span>
<span class="nv">$ </span><span class="nb">cat </span>20230702-142534F_20230702-142548I/backup.manifest |grep <span class="s1">'"reference":"20230702-142534F"'</span> |wc <span class="nt">-l</span>
969

<span class="c"># Second incremental</span>
<span class="nv">$ </span><span class="nb">cat </span>20230702-142534F_20230702-142601I/backup.manifest |grep <span class="s1">'"reference":"20230702-142534F"'</span> |wc <span class="nt">-l</span>
969
<span class="nv">$ </span><span class="nb">cat </span>20230702-142534F_20230702-142601I/backup.manifest |grep <span class="s1">'"reference":"20230702-142534F_20230702-142548I"'</span> |wc <span class="nt">-l</span>
3
</code></pre></div></div>

<p>So, in this case, to be able to restore the second incremental backup, we’d need 3 files from the first incremental. If for any reason, one of those 3 files gets corrupted, we might not be able to successfully recover.</p>

<p>And that’s the reason why there are differential backups. As you’ve seen in the <code class="language-plaintext highlighter-rouge">backup reference list</code> section, the differential backups only need the files from the full backup.</p>

<hr />

<h1 id="conclusion">Conclusion</h1>

<p>I hope this short post will now give you a better understanding of what incremental and differential backups mean for pgBackRest.</p>

<p>It is also important to remember that WAL archives are a key part of the backups. They are needed for consistency and let you perform <em>Point-In-Time Recovery</em>. But also, as long as you have all the WAL archives needed, in case your latest backup gets corrupted, you should be able to recover from a previous backup: the recovery would only be slower given it would have more WAL entries to replay.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[One of the most frequent questions I get is to actually explain the difference between differential and incremental backups in pgBackRest. As everyone might easily imagine, an incremental backup will only copy the database files (to keep it simple) that have been modified since the last successful backup. If you have a full backup every Sunday, and then an incremental every other day, the backup from Wednesday will be based on the one from Tuesday. That also means that if the backup from Monday or Tuesday is broken/corrupted (for any reason), you might not be able to recover from your Wednesday backup. Now, what is a differential backup then? It is an incremental backup but it will be based on the last successful full backup. So if you have a full backup every Sunday, and then a differential every other day, the backup from Wednesday will be based on the one from Sunday! It would then doesn’t matter if the other differential backups get broken/corrupted, but since you’ll always compare what has been modified since Sunday, the size of the differential backups will be bigger over time. Let’s have a concrete example.]]></summary></entry><entry><title type="html">pgBackRest SFTP support</title><link href="https://pgstef.github.io/2023/05/26/pgbackrest_sftp_support.html" rel="alternate" type="text/html" title="pgBackRest SFTP support" /><published>2023-05-26T12:55:00+02:00</published><updated>2023-05-26T12:55:00+02:00</updated><id>https://pgstef.github.io/2023/05/26/pgbackrest_sftp_support</id><content type="html" xml:base="https://pgstef.github.io/2023/05/26/pgbackrest_sftp_support.html"><![CDATA[<p>SFTP support has been added in the <a href="https://github.com/pgbackrest/pgbackrest/releases/tag/release%2F2.46">2.46</a> release on 22 May 2022.</p>

<p>In this demo setup, the SFTP host will be called <strong>sftp-srv</strong> and the PostgreSQL node <strong>pg-srv</strong>. Both nodes will be running on Rocky Linux 8.</p>

<p>If you’re familiar with Vagrant, here’s a simple <code class="language-plaintext highlighter-rouge">Vagrantfile</code> to initiate 3 virtual machines using those names:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Vagrantfile</span>
<span class="no">Vagrant</span><span class="p">.</span><span class="nf">configure</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
    <span class="n">config</span><span class="p">.</span><span class="nf">vm</span><span class="p">.</span><span class="nf">box</span> <span class="o">=</span> <span class="s1">'rockylinux/8'</span>
    <span class="n">config</span><span class="p">.</span><span class="nf">vm</span><span class="p">.</span><span class="nf">provider</span> <span class="s1">'libvirt'</span> <span class="k">do</span> <span class="o">|</span><span class="n">lv</span><span class="o">|</span>
        <span class="n">lv</span><span class="p">.</span><span class="nf">cpus</span> <span class="o">=</span> <span class="mi">1</span>
        <span class="n">lv</span><span class="p">.</span><span class="nf">memory</span> <span class="o">=</span> <span class="mi">1024</span>
    <span class="k">end</span>
    <span class="n">config</span><span class="p">.</span><span class="nf">vm</span><span class="p">.</span><span class="nf">synced_folder</span> <span class="s2">"."</span><span class="p">,</span> <span class="s2">"/vagrant"</span><span class="p">,</span> <span class="ss">disabled: </span><span class="kp">true</span>

    <span class="n">nodes</span>  <span class="o">=</span> <span class="s1">'sftp-srv'</span><span class="p">,</span> <span class="s1">'pg-srv'</span>
    <span class="n">nodes</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">node</span><span class="o">|</span>
        <span class="n">config</span><span class="p">.</span><span class="nf">vm</span><span class="p">.</span><span class="nf">define</span> <span class="n">node</span> <span class="k">do</span> <span class="o">|</span><span class="n">conf</span><span class="o">|</span>
            <span class="n">conf</span><span class="p">.</span><span class="nf">vm</span><span class="p">.</span><span class="nf">hostname</span> <span class="o">=</span> <span class="n">node</span>
        <span class="k">end</span>
    <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>When using Vagrant boxes, it might be needed to enable the SSH password authentication to proceed with SSH key exchange:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-i</span>
root# <span class="nb">sed</span> <span class="nt">-i</span> <span class="s1">'s/PasswordAuthentication no/PasswordAuthentication yes/g'</span> /etc/ssh/sshd_config    
root# systemctl restart sshd.service
root# passwd
</code></pre></div></div>

<!--MORE-->

<hr />

<h1 id="installation">Installation</h1>

<p>On the database node, first configure the PGDG repositories and install PostgreSQL:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">-y</span> https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm
<span class="nv">$ </span><span class="nb">sudo </span>dnf <span class="nt">-qy</span> module disable postgresql
<span class="nv">$ </span><span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">-y</span> postgresql15-server postgresql15-contrib
</code></pre></div></div>

<p>Create a basic PostgreSQL instance:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span><span class="nv">PGSETUP_INITDB_OPTIONS</span><span class="o">=</span><span class="s2">"--data-checksums"</span> /usr/pgsql-15/bin/postgresql-15-setup initdb
<span class="nv">$ </span><span class="nb">sudo </span>systemctl <span class="nb">enable </span>postgresql-15
<span class="nv">$ </span><span class="nb">sudo </span>systemctl start postgresql-15
</code></pre></div></div>

<p>Install pgBackRest and check its version:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">-y</span> epel-release
<span class="nv">$ </span><span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">-y</span> pgbackrest
<span class="nv">$ </span>pgbackrest version
pgBackRest 2.46
</code></pre></div></div>

<h2 id="create-a-dedicated-user-and-location-on-the-sftp-server">Create a dedicated user and location on the SFTP server</h2>

<p>The <code class="language-plaintext highlighter-rouge">pgbackrest</code> user is created to own the backups and archives repository on the SFTP server:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>groupadd pgbackrest
<span class="nv">$ </span><span class="nb">sudo </span>adduser <span class="nt">-g</span> pgbackrest <span class="nt">-n</span> pgbackrest
<span class="nv">$ </span><span class="nb">sudo mkdir</span> <span class="nt">-m</span> 750 <span class="nt">-p</span> /backup_space
<span class="nv">$ </span><span class="nb">sudo chown </span>pgbackrest: /backup_space
<span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-u</span> pgbackrest <span class="nb">mkdir</span> <span class="nt">-m</span> 750 <span class="nt">-p</span> /home/pgbackrest/.ssh
</code></pre></div></div>

<h2 id="setup-ssh-communication-between-the-postgresql-node-and-sftp-server">Setup SSH communication between the PostgreSQL node and SFTP server</h2>

<p>The idea is to generate a dedicated SSH key pair for the SFTP connection and use a secured passphrase with it.</p>

<p>First of all, generate the SSH key pair on the PostgreSQL node:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-u</span> postgres ssh-keygen <span class="nt">-f</span> /var/lib/pgsql/.ssh/id_rsa_sftp <span class="nt">-t</span> rsa <span class="nt">-b</span> 4096 <span class="nt">-N</span> <span class="s2">"BeSureToGenerateAndUseASecurePassphrase"</span>
</code></pre></div></div>

<p>Then, copy the generated public keys to the SFTP server to authorize it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># From sftp-srv</span>
<span class="nv">$ </span>ssh root@pg-srv <span class="nb">cat</span> /var/lib/pgsql/.ssh/id_rsa_sftp.pub| <span class="se">\</span>
    <span class="nb">sudo</span> <span class="nt">-u</span> pgbackrest <span class="nb">tee</span> <span class="nt">-a</span> /home/pgbackrest/.ssh/authorized_keys
</code></pre></div></div>

<p>The connection from the PostgreSQL node should now prompt for the passphrase:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-u</span> postgres ssh pgbackrest@sftp-srv <span class="nt">-i</span> /var/lib/pgsql/.ssh/id_rsa_sftp
</code></pre></div></div>

<hr />

<h1 id="configuration">Configuration</h1>

<p>We’ll now prepare the configuration for our stanza called <code class="language-plaintext highlighter-rouge">demo</code>.</p>

<p>Update the <strong>pg-srv</strong> pgBackRest configuration file:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># /etc/pgbackrest.conf
</span><span class="nn">[global]</span>
<span class="py">repo1-bundle</span><span class="p">=</span><span class="s">y</span>
<span class="py">repo1-type</span><span class="p">=</span><span class="s">sftp</span>
<span class="py">repo1-path</span><span class="p">=</span><span class="s">/backup_space</span>
<span class="py">repo1-sftp-host</span><span class="p">=</span><span class="s">sftp-srv</span>
<span class="py">repo1-sftp-host-key-hash-type</span><span class="p">=</span><span class="s">sha1</span>
<span class="py">repo1-sftp-host-user</span><span class="p">=</span><span class="s">pgbackrest</span>
<span class="py">repo1-sftp-private-key-file</span><span class="p">=</span><span class="s">/var/lib/pgsql/.ssh/id_rsa_sftp</span>
<span class="py">repo1-sftp-private-key-passphrase</span><span class="p">=</span><span class="s">BeSureToGenerateAndUseASecurePassphrase</span>

<span class="py">repo1-retention-full</span><span class="p">=</span><span class="s">2</span>
<span class="py">start-fast</span><span class="p">=</span><span class="s">y</span>
<span class="py">log-level-console</span><span class="p">=</span><span class="s">info</span>
<span class="py">log-level-file</span><span class="p">=</span><span class="s">debug</span>
<span class="py">delta</span><span class="p">=</span><span class="s">y</span>
<span class="py">process-max</span><span class="p">=</span><span class="s">2</span>

<span class="nn">[demo]</span>
<span class="py">pg1-path</span><span class="p">=</span><span class="s">/var/lib/pgsql/15/data</span>
</code></pre></div></div>

<p>Configure archiving in the <code class="language-plaintext highlighter-rouge">postgresql.conf</code> file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>listen_addresses = '*'
archive_mode = on
archive_command = 'pgbackrest --stanza=demo archive-push %p'
</code></pre></div></div>

<p>The PostgreSQL instance must be restarted after making these changes.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>systemctl restart postgresql-15.service
</code></pre></div></div>

<p>When using SFTP, pgBackRest commands are run like with other repository types (NFS, S3,…).
Let’s then now create the stanza and check the configuration on the <strong>pg-srv</strong>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo stanza-create
...
P00   INFO: stanza-create <span class="nb">command </span>end: completed successfully

<span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo check
...
P00   INFO: check <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>We can now take our first backup from <strong>pg-srv</strong>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo <span class="nt">--type</span><span class="o">=</span>full backup
P00   INFO: backup <span class="nb">command </span>begin 2.46: ...
P00   INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
P00   INFO: backup start archive <span class="o">=</span> 000000010000000000000003, lsn <span class="o">=</span> 0/3000028
P00   INFO: check archive <span class="k">for </span>prior segment 000000010000000000000002
P00   INFO: execute non-exclusive backup stop and <span class="nb">wait </span><span class="k">for </span>all WAL segments to archive
P00   INFO: backup stop archive <span class="o">=</span> 000000010000000000000003, lsn <span class="o">=</span> 0/3000138
P00   INFO: check archive <span class="k">for </span>segment<span class="o">(</span>s<span class="o">)</span> 000000010000000000000003:000000010000000000000003
P00   INFO: new backup label <span class="o">=</span> 20230525-140532F
P00   INFO: full backup size <span class="o">=</span> 22MB, file total <span class="o">=</span> 966
P00   INFO: backup <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>Finally, look at the repository content using the <code class="language-plaintext highlighter-rouge">info</code> command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo info
stanza: demo
    status: ok
    cipher: none

    db <span class="o">(</span>current<span class="o">)</span>
        wal archive min/max <span class="o">(</span>15<span class="o">)</span>: 000000010000000000000001/000000010000000000000003

        full backup: 20230525-140532F
            timestamp start/stop: 2023-05-25 14:05:32 / 2023-05-25 14:05:44
            wal start/stop: 000000010000000000000003 / 000000010000000000000003
            database size: 22MB, database backup size: 22MB
            repo1: backup <span class="nb">set </span>size: 2.9MB, backup size: 2.9MB
</code></pre></div></div>

<hr />

<h1 id="conclusion">Conclusion</h1>

<p>The SFTP repository type is pretty easy to setup but as mentioned in the official documentation, the SFTP file transfer is relatively slow.
Increasing <code class="language-plaintext highlighter-rouge">process-max</code> to parallelize file transfer and using file bundling will help but it would probably be better to use the other supported repository types if you can.</p>

<p>Edit: pgBackRest <a href="https://github.com/pgbackrest/pgbackrest/releases/tag/release%2F2.47">2.47</a> brought a significant performance improvement of the SFTP storage driver. So it becomes a good option if you really need to use it.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[SFTP support has been added in the 2.46 release on 22 May 2022. In this demo setup, the SFTP host will be called sftp-srv and the PostgreSQL node pg-srv. Both nodes will be running on Rocky Linux 8. If you’re familiar with Vagrant, here’s a simple Vagrantfile to initiate 3 virtual machines using those names: # Vagrantfile Vagrant.configure(2) do |config| config.vm.box = 'rockylinux/8' config.vm.provider 'libvirt' do |lv| lv.cpus = 1 lv.memory = 1024 end config.vm.synced_folder ".", "/vagrant", disabled: true nodes = 'sftp-srv', 'pg-srv' nodes.each do |node| config.vm.define node do |conf| conf.vm.hostname = node end end end When using Vagrant boxes, it might be needed to enable the SSH password authentication to proceed with SSH key exchange: $ sudo -i root# sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config root# systemctl restart sshd.service root# passwd]]></summary></entry><entry><title type="html">pgBackRest 2.41 released</title><link href="https://pgstef.github.io/2022/09/20/pgbackrest_2_41_released.html" rel="alternate" type="text/html" title="pgBackRest 2.41 released" /><published>2022-09-20T18:00:00+02:00</published><updated>2022-09-20T18:00:00+02:00</updated><id>https://pgstef.github.io/2022/09/20/pgbackrest_2_41_released</id><content type="html" xml:base="https://pgstef.github.io/2022/09/20/pgbackrest_2_41_released.html"><![CDATA[<p>With pgBackRest <a href="https://github.com/pgbackrest/pgbackrest/releases/tag/release/2.41">2.41</a> just released, a new feature called <strong>backup annotations</strong> is now available.
Let’s see in this blog post what this is about.</p>

<!--MORE-->

<hr />

<h1 id="backup-annotations">Backup annotations</h1>

<p>This new feature adds an <code class="language-plaintext highlighter-rouge">--annotation</code> option to the <strong>backup</strong> command.
We can now attach informative key/value pairs to the backup and the option may be used multiple times to attach multiple annotations.</p>

<p>Example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ pgbackrest backup --stanza=X --type=full \
  --annotation=comment="this is our initial backup" \
  --annotation=some-other-key="any text you'd like"
</code></pre></div></div>

<p>Internally, the initial annotation is stored in the shared backup information:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ pgbackrest repo-get backup/X/backup.info |grep annotation
..."backup-annotation":{"comment":"this is our initial backup","some-other-key":"any text you'd like"}...
</code></pre></div></div>

<p>That allows to show those annotations in the <strong>info</strong> command.</p>

<p>First, in the <em>text output</em>, for a specific backup <code class="language-plaintext highlighter-rouge">--set</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ pgbackrest info --stanza=X --set=20220920-140720F
...
full backup: 20220920-140720F
    timestamp start/stop: 2022-09-20 14:07:20 / 2022-09-20 14:07:24
    wal start/stop: 00000002000000000000002F / 00000002000000000000002F
    lsn start/stop: 0/2F000028 / 0/2F000100
    database size: 185.3MB, database backup size: 185.3MB
    repo1: backup set size: 13.7MB, backup size: 13.7MB
    database list: bench (16394), postgres (13631)
    annotation(s)
        comment: this is our initial backup
        some-other-key: any text you'd like

$ pgbackrest info --stanza=X --set=20220920-140740F
...
full backup: 20220920-140740F
    timestamp start/stop: 2022-09-20 14:07:40 / 2022-09-20 14:07:46
    wal start/stop: 000000020000000000000031 / 000000020000000000000031
    lsn start/stop: 0/31000028 / 0/31000138
    database size: 185.3MB, database backup size: 185.3MB
    repo1: backup set size: 13.7MB, backup size: 13.7MB
    database list: bench (16394), postgres (13631)
    annotation(s)
        comment: just another backup
</code></pre></div></div>

<p>Then also in the global <em>json output</em>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pgbackrest info <span class="nt">--stanza</span><span class="o">=</span>X <span class="nt">--output</span><span class="o">=</span>json
...<span class="o">{</span><span class="s2">"annotation"</span>:<span class="o">{</span><span class="s2">"comment"</span>:<span class="s2">"this is our initial backup"</span>,<span class="s2">"some-other-key"</span>:<span class="s2">"any text you'd like"</span><span class="o">}</span>...
...<span class="o">{</span><span class="s2">"annotation"</span>:<span class="o">{</span><span class="s2">"comment"</span>:<span class="s2">"just another backup"</span><span class="o">}</span>...
</code></pre></div></div>

<p>pgBackRest 2.41 also adds support for the <code class="language-plaintext highlighter-rouge">--set</code> option for the <em>json output</em> of the <strong>info</strong> command!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ pgbackrest info --stanza=X --set=20220920-140720F --output=json
...{"annotation":{"comment":"this is our initial backup","some-other-key":"any text you'd like"}...

$ pgbackrest info --stanza=X --set=20220920-140740F --output=json
...{"annotation":{"comment":"just another backup"}...
</code></pre></div></div>

<p>Finally, there’s a new <strong>annotate</strong> command to add, modify and remove annotations.</p>

<p>Example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ pgbackrest annotate --stanza=X --set=20220920-140740F \
  --annotation=comment="another brick in the wall"
$ pgbackrest info --stanza=X --set=20220920-140740F
...
    annotation(s)
        comment: another brick in the wall
</code></pre></div></div>

<p>The keys may also be modified or removed:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ pgbackrest annotate --stanza=X --set=20220920-140740F \
  --annotation=comment= --annotation=new-comment="just a backup"
$ pgbackrest info --stanza=X --set=20220920-140740F
...
annotation(s)
    new-comment: just a backup
</code></pre></div></div>

<hr />

<h1 id="conclusion">Conclusion</h1>

<p>It is now your turn to update your pgBackRest version and try this new feature :-)</p>]]></content><author><name></name></author><summary type="html"><![CDATA[With pgBackRest 2.41 just released, a new feature called backup annotations is now available. Let’s see in this blog post what this is about.]]></summary></entry><entry><title type="html">Patroni and pgBackRest combined</title><link href="https://pgstef.github.io/2022/07/12/patroni_and_pgbackrest_combined.html" rel="alternate" type="text/html" title="Patroni and pgBackRest combined" /><published>2022-07-12T09:00:00+02:00</published><updated>2022-07-12T09:00:00+02:00</updated><id>https://pgstef.github.io/2022/07/12/patroni_and_pgbackrest_combined</id><content type="html" xml:base="https://pgstef.github.io/2022/07/12/patroni_and_pgbackrest_combined.html"><![CDATA[<p>I see more and more questions about pgBackRest in a Patroni cluster on community channels.
So, following yesterday’s post about Patroni on pure Raft, we’ll see in this post an example about how to setup pgBackRest in such cases.</p>

<p>To prepare this post, I followed most of the instructions given by Federico Campoli at PGDAY RUSSIA 2021 about <a href="https://pgday.ru/presentation/298/60f04d9ae4105.pdf">Protecting your data with Patroni and pgbackrest</a>. The video recording might even be found <a href="https://youtu.be/UNM6if3uIUk">here</a>.</p>

<!--MORE-->

<hr />

<h1 id="pgbackrest-repository-location">pgBackRest repository location</h1>

<p>The first decision you’ll have to take is to know where to store your pgBackRest repository. It can be any supported <a href="https://pgbackrest.org/configuration.html#section-repository/option-repo-type">repo-type</a> with or without a dedicated backup server (aka. <a href="https://pgbackrest.org/user-guide-rhel.html#repo-host">repo-host</a>). The most important thing to remember is that the repository should be equally reachable from all your PostgreSQL/Patroni nodes.</p>

<p>Based on that decision, you’ll have to think about how to schedule the backup command. With a repo-host that’s easy, because pgBackRest will be able to determine which PostgreSQL node is the primary (or a standby if you want to take the <a href="https://pgbackrest.org/user-guide-rhel.html#standby-backup">backup from a standby node</a>). If you’re using a directly attached storage (i.e. NFS mount point or S3 bucket), the easiest solution might be to test the return of <code class="language-plaintext highlighter-rouge">pg_is_in_recovery()</code> in the <a href="https://pgbackrest.org/user-guide-rhel.html#quickstart/schedule-backup">cron</a> job.</p>

<p>For the purpose of this demo setup, I’ll use a MinIO docker container with self-signed certificates and a bucket named <code class="language-plaintext highlighter-rouge">pgbackrest</code> already created.</p>

<p><img src="../../../images/pgbackrest-patroni-minio.png" alt="" width="800px" /></p>

<hr />

<h1 id="installation">Installation</h1>

<h2 id="pgbackrest-installation-and-configuration">pgBackRest installation and configuration</h2>

<p>Install pgBackRest on all the PostgreSQL nodes:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">-y</span> epel-release
<span class="nv">$ </span><span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">-y</span> pgbackrest
<span class="nv">$ </span>pgbackrest version
pgBackRest 2.39
</code></pre></div></div>

<p>Then, configure <code class="language-plaintext highlighter-rouge">/etc/pgbackrest.conf</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat</span> <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh"> | sudo tee /etc/pgbackrest.conf
[global]
repo1-type=s3
repo1-storage-verify-tls=n
repo1-s3-endpoint=192.168.121.1
repo1-s3-uri-style=path
repo1-s3-bucket=pgbackrest
repo1-s3-key=minioadmin
repo1-s3-key-secret=minioadmin
repo1-s3-region=eu-west-3

repo1-path=/repo1
repo1-retention-full=2
start-fast=y
log-level-console=info
log-level-file=debug
delta=y
process-max=2

[demo-cluster-1]
pg1-path=/var/lib/pgsql/14/data
pg1-port=5432
pg1-user=postgres
</span><span class="no">EOF
</span></code></pre></div></div>

<p>In the previous post, we defined the cluster name to use in the <code class="language-plaintext highlighter-rouge">scope</code> configuration of Patroni. We’ll reuse the same name for the stanza name.</p>

<p>Let’s initialize the stanza:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo-cluster-1 stanza-create
INFO: stanza-create <span class="nb">command </span>begin 2.39: ...
INFO: stanza-create <span class="k">for </span>stanza <span class="s1">'demo-cluster-1'</span> on repo1
INFO: stanza-create <span class="nb">command </span>end: completed successfully <span class="o">(</span>684ms<span class="o">)</span>
</code></pre></div></div>

<h2 id="configure-patroni-to-use-pgbackrest">Configure Patroni to use pgBackRest</h2>

<p>Let’s adjust the <code class="language-plaintext highlighter-rouge">archive_command</code> in Patroni configuration:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres patronictl <span class="nt">-c</span> /etc/patroni.yml edit-config
<span class="c">## adjust the following lines</span>
postgresql:
  parameters:
    archive_command: pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo-cluster-1 archive-push <span class="s2">"%p"</span>
    archive_mode: <span class="s2">"on"</span>

<span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres patronictl <span class="nt">-c</span> /etc/patroni.yml reload demo-cluster-1
</code></pre></div></div>

<p>Check that the archiving system is working:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo-cluster-1 check
INFO: check <span class="nb">command </span>begin 2.39: ...
INFO: check repo1 configuration <span class="o">(</span>primary<span class="o">)</span>
INFO: check repo1 archive <span class="k">for </span>WAL <span class="o">(</span>primary<span class="o">)</span>
INFO: WAL segment 000000010000000000000004 successfully archived to <span class="s1">'...'</span> on repo1
INFO: check <span class="nb">command </span>end: completed successfully <span class="o">(</span>1083ms<span class="o">)</span>
</code></pre></div></div>

<h2 id="take-a-first-backup">Take a first backup</h2>

<p>Let’s not take our first full backup:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo-cluster-1 backup <span class="nt">--type</span><span class="o">=</span>full
P00   INFO: backup <span class="nb">command </span>begin 2.39: ...
P00   INFO: execute non-exclusive pg_start_backup<span class="o">()</span>:
  backup begins after the requested immediate checkpoint completes
P00   INFO: backup start archive <span class="o">=</span> 000000010000000000000006, lsn <span class="o">=</span> 0/6000028
P00   INFO: check archive <span class="k">for </span>prior segment 000000010000000000000005
P00   INFO: execute non-exclusive pg_stop_backup<span class="o">()</span> and <span class="nb">wait </span><span class="k">for </span>all WAL segments to archive
P00   INFO: backup stop archive <span class="o">=</span> 000000010000000000000006, lsn <span class="o">=</span> 0/6000100
P00   INFO: check archive <span class="k">for </span>segment<span class="o">(</span>s<span class="o">)</span> 000000010000000000000006:000000010000000000000006
P00   INFO: new backup label <span class="o">=</span> 20220711-075256F
P00   INFO: full backup size <span class="o">=</span> 25.2MB, file total <span class="o">=</span> 957
P00   INFO: backup <span class="nb">command </span>end: completed successfully
</code></pre></div></div>

<p>All Patroni nodes should now be able to see the content of the pgBackRest repository:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pgbackrest info
stanza: demo-cluster-1
    status: ok
    cipher: none

    db <span class="o">(</span>current<span class="o">)</span>
        wal archive min/max <span class="o">(</span>14<span class="o">)</span>: 000000010000000000000006/000000010000000000000006

        full backup: 20220711-075256F
            timestamp start/stop: 2022-07-11 07:52:56 / 2022-07-11 07:53:02
            wal start/stop: 000000010000000000000006 / 000000010000000000000006
            database size: 25.2MB, database backup size: 25.2MB
            repo1: backup <span class="nb">set </span>size: 3.2MB, backup size: 3.2MB
</code></pre></div></div>

<hr />

<h1 id="the-restore-part">The “restore” part</h1>

<p>Now that the archiving system is working, we can configure the <code class="language-plaintext highlighter-rouge">restore_command</code>. Possibly, we could disable replication slots for Patroni too since we now have the archives as safety net for the replication.</p>

<p>Let’s edit the bootstrap configuration part:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres patronictl <span class="nt">-c</span> /etc/patroni.yml edit-config
<span class="c">## adjust the following lines</span>
loop_wait: 10
maximum_lag_on_failover: 1048576
postgresql:
  parameters:
    archive_command: pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo-cluster-1 archive-push <span class="s2">"%p"</span>
    archive_mode: <span class="s1">'on'</span>
  recovery_conf:
    recovery_target_timeline: latest
    restore_command: pgbackrest <span class="nt">--stanza</span><span class="o">=</span>demo-cluster-1 archive-get %f <span class="s2">"%p"</span>
  use_pg_rewind: <span class="nb">false
  </span>use_slots: <span class="nb">true
</span>retry_timeout: 10
ttl: 30

<span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres patronictl <span class="nt">-c</span> /etc/patroni.yml reload demo-cluster-1
</code></pre></div></div>

<p>To use pgBackRest for creating (or re-initializing) replicas, we need to adjust the Patroni configuration file.</p>

<p>On all your nodes, in <code class="language-plaintext highlighter-rouge">/etc/patroni.yml</code>, find the following part:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">postgresql</span><span class="pi">:</span>
  <span class="na">listen</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0.0.0.0:5432"</span>
  <span class="na">connect_address</span><span class="pi">:</span> <span class="s2">"</span><span class="s">$MY_IP:5432"</span>
  <span class="na">data_dir</span><span class="pi">:</span> <span class="s">/var/lib/pgsql/14/data</span>
  <span class="na">bin_dir</span><span class="pi">:</span> <span class="s">/usr/pgsql-14/bin</span>
  <span class="na">pgpass</span><span class="pi">:</span> <span class="s">/tmp/pgpass0</span>
  <span class="na">authentication</span><span class="pi">:</span>
    <span class="na">replication</span><span class="pi">:</span>
      <span class="na">username</span><span class="pi">:</span> <span class="s">replicator</span>
      <span class="na">password</span><span class="pi">:</span> <span class="s">confidential</span>
    <span class="na">superuser</span><span class="pi">:</span>
      <span class="na">username</span><span class="pi">:</span> <span class="s">postgres</span>
      <span class="na">password</span><span class="pi">:</span> <span class="s">my-super-password</span>
    <span class="na">rewind</span><span class="pi">:</span>
      <span class="na">username</span><span class="pi">:</span> <span class="s">rewind_user</span>
      <span class="na">password</span><span class="pi">:</span> <span class="s">rewind_password</span>
  <span class="na">parameters</span><span class="pi">:</span>
    <span class="na">unix_socket_directories</span><span class="pi">:</span> <span class="s1">'</span><span class="s">/var/run/postgresql,/tmp'</span>
</code></pre></div></div>

<p>and add:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="na">create_replica_methods</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">pgbackrest</span>
    <span class="pi">-</span> <span class="s">basebackup</span>
  <span class="na">pgbackrest</span><span class="pi">:</span>
    <span class="na">command</span><span class="pi">:</span> <span class="s">pgbackrest --stanza=demo-cluster-1 restore --type=none</span>
    <span class="na">keep_data</span><span class="pi">:</span> <span class="s">True</span>
    <span class="na">no_params</span><span class="pi">:</span> <span class="s">True</span>
  <span class="na">basebackup</span><span class="pi">:</span>
    <span class="na">checkpoint</span><span class="pi">:</span> <span class="s1">'</span><span class="s">fast'</span>
</code></pre></div></div>

<p>Don’t forget to reload the configuration:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>systemctl reload patroni
</code></pre></div></div>

<h2 id="create-a-replica-using-pgbackrest">Create a replica using pgBackRest</h2>

<p>Here is our current situation:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres patronictl <span class="nt">-c</span> /etc/patroni.yml list
+--------+-----------------+---------+---------+----+-----------+
| Member | Host            | Role    | State   | TL | Lag <span class="k">in </span>MB |
+ Cluster: demo-cluster-1 <span class="o">(</span>7119014497759128647<span class="o">)</span> <span class="nt">----</span>+-----------+
| srv1   | 192.168.121.2   | Replica | running |  1 |         0 |
| srv2   | 192.168.121.254 | Leader  | running |  1 |           |
| srv3   | 192.168.121.89  | Replica | running |  1 |         0 |
+--------+-----------------+---------+---------+----+-----------+
</code></pre></div></div>

<p>We already have 2 running replicas. So we’ll need to stop Patroni on one node and remove its data directory to trigger a new replica creation:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>systemctl stop patroni
<span class="nv">$ </span><span class="nb">sudo rm</span> <span class="nt">-rf</span> /var/lib/pgsql/14/data
<span class="nv">$ </span><span class="nb">sudo </span>systemctl start patroni
<span class="nv">$ </span><span class="nb">sudo </span>journalctl <span class="nt">-u</span> patroni.service <span class="nt">-f</span>
...
INFO: trying to bootstrap from leader <span class="s1">'srv2'</span>
...
INFO: replica has been created using pgbackrest
INFO: bootstrapped from leader <span class="s1">'srv2'</span>
...
INFO: no action. I am <span class="o">(</span>srv3<span class="o">)</span>, a secondary, and following a leader <span class="o">(</span>srv2<span class="o">)</span>
</code></pre></div></div>

<p>As we can see from the logs above, the replica has successfully been created using pgBackRest:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres patronictl <span class="nt">-c</span> /etc/patroni.yml list
+--------+-----------------+---------+---------+----+-----------+
| Member | Host            | Role    | State   | TL | Lag <span class="k">in </span>MB |
+ Cluster: demo-cluster-1 <span class="o">(</span>7119014497759128647<span class="o">)</span> <span class="nt">----</span>+-----------+
| srv1   | 192.168.121.2   | Replica | running |  1 |         0 |
| srv2   | 192.168.121.254 | Leader  | running |  1 |           |
| srv3   | 192.168.121.89  | Replica | running |  1 |         0 |
+--------+-----------------+---------+---------+----+-----------+
</code></pre></div></div>

<p>Now, let’s insert some data on srv2 and simulate an incident by suspending the VM network interface for a few seconds.</p>

<p>A failover will happen and the old primary will be completely out-of-sync and the replication will be lagging when adding new data on the primary:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres patronictl <span class="nt">-c</span> /etc/patroni.yml list
+--------+-----------------+---------+---------+----+-----------+
| Member | Host            | Role    | State   | TL | Lag <span class="k">in </span>MB |
+ Cluster: demo-cluster-1 <span class="o">(</span>7119014497759128647<span class="o">)</span> <span class="nt">----</span>+-----------+
| srv1   | 192.168.121.2   | Leader  | running |  2 |           |
| srv2   | 192.168.121.254 | Replica | running |  1 |       188 |
| srv3   | 192.168.121.89  | Replica | running |  2 |         0 |
+--------+-----------------+---------+---------+----+-----------+
</code></pre></div></div>

<p>Since we didn’t configure <code class="language-plaintext highlighter-rouge">pg_rewind</code>, we’ll need to re-initialize the failing node manually:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres patronictl <span class="nt">-c</span> /etc/patroni.yml reinit demo-cluster-1 srv2
+--------+-----------------+---------+---------+----+-----------+
| Member | Host            | Role    | State   | TL | Lag <span class="k">in </span>MB |
+ Cluster: demo-cluster-1 <span class="o">(</span>7119014497759128647<span class="o">)</span> <span class="nt">----</span>+-----------+
| srv1   | 192.168.121.2   | Leader  | running |  2 |           |
| srv2   | 192.168.121.254 | Replica | running |  1 |       188 |
| srv3   | 192.168.121.89  | Replica | running |  2 |         0 |
+--------+-----------------+---------+---------+----+-----------+
Are you sure you want to reinitialize members srv2? <span class="o">[</span>y/N]: y
Success: reinitialize <span class="k">for </span>member srv2

<span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-iu</span> postgres patronictl <span class="nt">-c</span> /etc/patroni.yml list
+--------+-----------------+---------+---------+----+-----------+
| Member | Host            | Role    | State   | TL | Lag <span class="k">in </span>MB |
+ Cluster: demo-cluster-1 <span class="o">(</span>7119014497759128647<span class="o">)</span> <span class="nt">----</span>+-----------+
| srv1   | 192.168.121.2   | Leader  | running |  2 |           |
| srv2   | 192.168.121.254 | Replica | running |  2 |         0 |
| srv3   | 192.168.121.89  | Replica | running |  2 |         0 |
+--------+-----------------+---------+---------+----+-----------+
</code></pre></div></div>

<p>The following trace shows that pgBackRest has successfully been used to re-initialize the old primary:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>INFO: replica has been created using pgbackrest
INFO: bootstrapped from leader <span class="s1">'srv1'</span>
...
INFO: Lock owner: srv1<span class="p">;</span> I am srv2
INFO: establishing a new patroni connection to the postgres cluster
INFO: no action. I am <span class="o">(</span>srv2<span class="o">)</span>, a secondary, and following a leader <span class="o">(</span>srv1<span class="o">)</span>
</code></pre></div></div>

<hr />

<h1 id="conclusion">Conclusion</h1>

<p>It is very easy and convenient to configure Patroni to use pgBackRest. Obviously, having backups is a good thing but being able to use those backups as source for the replica creation or re-initialization is even better.</p>

<p>As usual, the hardest part is to clearly define where to store the pgBackRest repository.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I see more and more questions about pgBackRest in a Patroni cluster on community channels. So, following yesterday’s post about Patroni on pure Raft, we’ll see in this post an example about how to setup pgBackRest in such cases. To prepare this post, I followed most of the instructions given by Federico Campoli at PGDAY RUSSIA 2021 about Protecting your data with Patroni and pgbackrest. The video recording might even be found here.]]></summary></entry></feed>