<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Blog - System administration</title><link href="https://blog.linuxw.info/" rel="alternate"/><link href="https://blog.linuxw.info/feeds/system-administration.atom.xml" rel="self"/><id>https://blog.linuxw.info/</id><updated>2026-04-12T00:00:00+02:00</updated><entry><title>Streaming PostgreSQL backups with pg_dump and borgbackup</title><link href="https://blog.linuxw.info/streaming-postgresql-backups-with-pg_dump-and-borgbackup-en.html" rel="alternate"/><published>2026-04-12T00:00:00+02:00</published><updated>2026-04-12T00:00:00+02:00</updated><author><name>gileri</name></author><id>tag:blog.linuxw.info,2026-04-12:/streaming-postgresql-backups-with-pg_dump-and-borgbackup-en.html</id><summary type="html">&lt;p&gt;I run a &lt;a href="https://bitmagnet.io/"&gt;bitmagnet&lt;/a&gt; instance, an awesome Bittorrent DHT crawler and content classifier.
While the database is not really critical and could be crawled again in case of data loss, it would take a lot of time to reach its current completeness. As you imagine, there are quite a lot …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I run a &lt;a href="https://bitmagnet.io/"&gt;bitmagnet&lt;/a&gt; instance, an awesome Bittorrent DHT crawler and content classifier.
While the database is not really critical and could be crawled again in case of data loss, it would take a lot of time to reach its current completeness. As you imagine, there are quite a lot of active torrents (indexed 29 million as of 2026!).&lt;/p&gt;
&lt;p&gt;So I needed to back up the database. I identified two usual ways: back up the PostgreSQL files after making a Btrfs snapshot, or making a pg_dump of the DB.
I eliminated the first option, as backing up the indices was not optimal.&lt;/p&gt;
&lt;p&gt;pg_dump on the other hand required some reads and CPU time, and if used conventionally, a lot of disk storage (around 170GB) that is not really useful on the host. However we can work around the second limitation.&lt;/p&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;To avoid dumping the DB on the disk before it being picked up by &lt;a href="https://borgbackup.readthedocs.io/"&gt;borgbackup&lt;/a&gt;, we create a FIFO file, make pg_dump write into it, and finally make borgbackup read the file as a standard file with the &lt;a href="https://borgbackup.readthedocs.io/en/stable/usage/create.html"&gt;&lt;code&gt;--read-special&lt;/code&gt; flag&lt;/a&gt;.
pg_dump will wait until borgbackup is reading before writing, so no overhead until the backup is made.&lt;/p&gt;
&lt;p&gt;I'm using the borgbackup NixOS module, but as it's mostly simple bash, it could be used easily on other systems.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;services.borgbackup.jobs.example = {
  extraCreateArgs = &amp;quot;--read-special&amp;quot;;
  preHook = lib.concatMapStringsSep &amp;quot;\n&amp;quot; (
    db:
    let
      dumpfile = &amp;quot;/tmp/pg-dump-${db}.sql&amp;quot;;
    in
    &amp;#39;&amp;#39;
      ${pkgs.coreutils}/bin/mkfifo --mode=660 &amp;quot;${dumpfile}&amp;quot;
      ${pkgs.coreutils}/bin/chgrp postgres &amp;quot;${dumpfile}&amp;quot;
      ${pkgs.su}/bin/su postgres -c &amp;#39;${config.services.postgresql.package}/bin/pg_dump -f ${dumpfile} ${db}&amp;#39; &amp;amp;
    &amp;#39;&amp;#39;
  ) ([&amp;quot;database1&amp;quot;, &amp;quot;database2&amp;quot;]);
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;&amp;amp;&lt;/code&gt; is important here, as &lt;code&gt;pg_dump&lt;/code&gt; will not exit until the whole backup is read.&lt;/p&gt;</content><category term="System administration"/></entry></feed>