17 Dezember 2024

Kurzer Benchmark: Verbesserungen beim Dumping großer Objekte in Postgres 17

PostgreSQL Version 17 wurde vor einiger Zeit veröffentlicht. Eine der vielen Funktionen ist eine Änderung von Tom Lane mit dem Titel „Rearrange pg_dump’s handling of large objects for better efficiency“. In der Vergangenheit haben wir beobachtet, dass eine große Anzahl von großen Objekten bei unseren Kunden zu Performance-Problemen beim Dump/Restore führte. Der Hauptgrund dafür ist, dass große Objekte sich stark von TOAST (The Oversized Attribute Storage Technique) unterscheiden: Während TOAST-Daten für den Benutzer völlig transparent sind, werden große Objekte außerhalb der Zeile in einer pg_largeboject Tabelle gespeichert, wobei ein Link zur jeweiligen Zeile in dieser Tabelle eine OID in der Tabelle selbst ist.

Einführung in große
Objekte

Hier ist ein Beispiel, wie große Objekte verwendet werden können:

postgres=# CREATE TABLE test(id BIGINT, blob OID);
CREATE TABLE
postgres=# INSERT INTO test VALUES (1, lo_import('/etc/issue.net'));
INSERT 0 1
postgres=# SELECT * FROM test;
 id | blob
----+-------
  1 | 33280
(1 row)

postgres=# SELECT * FROM pg_largeobject;
 loid  | pageno |                    data
-------+--------+--------------------------------------------
 33280 |      0 | \x44656269616e20474e552f4c696e75782031320a
(1 row)

postgres=# SELECT lo_export(test.blob, '/tmp/foo') FROM test;
 lo_export
-----------
         1
(1 row)

postgres=# SELECT pg_read_file('/tmp/foo');
    pg_read_file
---------------------
 Debian GNU/Linux 12+

(1 row)

postgres=# INSERT INTO test VALUES (1, lo_import('/etc/issue.net'));
INSERT 0 1

Wenn wir nun die Datenbank im Custom-Format mit den Versionen 16 und 17 von pg_dump dumpen und dann pg_restore -l verwenden, um das Inhaltsverzeichnis (TOC) anzuzeigen, sehen wir einen Unterschied:

$ for version in 16 17; do /usr/lib/postgresql/$version/bin/pg_dump -Fc -f lo_test_$version.dmp; \
> pg_restore -l lo_test_$version.dmp | grep -v ^\; > lo_test_$version.toc; done
$ diff -u lo_test_{16,17}.toc
--- lo_test_16.toc  2024-12-11 09:05:46.550667808 +0100
+++ lo_test_17.toc  2024-12-11 09:05:46.594670235 +0100
@@ -1,5 +1,4 @@
 215; 1259 33277 TABLE public test postgres
-3348; 2613 33280 BLOB - 33280 postgres
-3349; 2613 33281 BLOB - 33281 postgres
+3348; 2613 33280 BLOB METADATA - 33280..33281 postgres
 3347; 0 33277 TABLE DATA public test postgres
-3350; 0 0 BLOBS - BLOBS
+3349; 0 0 BLOBS - 33280..33281 postgres

Der Dump mit Version 17 kombiniert die Metadaten der großen Objekte in BLOB METADATA, wodurch nur ein Eintrag im Inhaltsverzeichnis (TOC) für sie erstellt wird.

Ferner, wenn wir das Directory-Dump-Format verwenden, sehen wir, dass pg_dump für jedes große Objekt eine Datei erstellt:

$ pg_dump -Fd -f lo_test.dir
$ ls lo_test.dir/
3347.dat.gz  blob_33280.dat.gz  blob_33281.dat.gz  blobs.toc  toc.dat

Wenn es nur wenige große Objekte gibt, ist dies kein Problem. Wenn aber der Mechanismus für große Objekte verwendet wird, um Hunderttausende oder Millionen von großen Objekten zu erstellen, wird dies zu einem ernsthaften Problem für pg_dump/pg_restore.

Schließlich, um die großen Objekte vollständig zu entfernen, reicht es nicht aus, die Tabelle zu löschen, das große Objekt muss ebenfalls entlinkt werden:

postgres=# DROP TABLE test;
DROP TABLE
postgres=# SELECT COUNT(*) FROM pg_largeobject;
 count
-------
     2
(1 row)

postgres=# SELECT lo_unlink(loid) FROM pg_largeobject;
 lo_unlink
-----------
         1
         1
(2 rows)

postgres=# SELECT COUNT(*) FROM pg_largeobject;
 count
-------
     0
(1 row)

Benchmark

Wir generieren eine Million große Objekte in einer PostgreSQL 16 Instanz:

lotest=# SELECT lo_create(id) FROM generate_series(1,1000000) AS id;
 lo_create
-----------
         1
         2
[...]
    999999
   1000000
(1000000 rows)

lotest=# SELECT COUNT(*) FROM pg_largeobject_metadata;
  count
---------
 1000000
(1 row)
(1 row)

Wir dumpen nun die Datenbank mit pg_dump aus den Versionen 16 und 17, zuerst als Custom- und dann als Directory-Dump, wobei wir das time Dienstprogramm verwenden, um Laufzeit und Speichernutzung zu verfolgen:

$ for version in 16 17; do echo -n "$version: "; \
> /usr/bin/time -f '%E %Mk mem' /usr/lib/postgresql/$version/bin/pg_dump \
> -Fc -f lo_test_$version.dmp lotest; done
16: 0:36.73 755692k mem
17: 0:34.69 217776k mem
$ for version in 16 17; do echo -n "$version: "; \
> /usr/bin/time -f '%E %Mk mem' /usr/lib/postgresql/$version/bin/pg_dump \
> -Fd -f lo_test_$version.dir lotest; done
16: 8:23.48 755624k mem
17: 7:51.04 217980k mem

Das Dumping im Directory-Format dauert wesentlich länger als im Custom-Format, während der Speicherverbrauch für beide sehr ähnlich ist. Die Laufzeit ist für Version 17 im Vergleich zu Version 16 etwas geringer, aber der große Unterschied liegt im verwendeten Speicher, der 3,5-mal kleiner ist.

Auch wenn man die Dateigröße für den Custom-Dump oder die Dateigröße der Inhaltsverzeichnis-Datei (TOC) betrachtet, wird der Unterschied sehr deutlich:

$ ls -lh lo_test_1?.dmp | awk '{print $5 " " $9}'
211M lo_test_16.dmp
29M lo_test_17.dmp
$ ls -lh lo_test_1?.dir/toc.dat | awk '{print $5 " " $9}'
185M lo_test_16.dir/toc.dat
6,9M lo_test_17.dir/toc.dat

Der Custom-Dump ist etwa 7-mal kleiner, während die TOC-Datei des Directory-Dumps etwa 25-mal kleiner ist. Wir haben auch verschiedene Anzahlen großer Objekte getestet (von 50.000 bis 1,5 Millionen) und fanden nur eine geringe Abweichung in diesen Verhältnissen: Das Verhältnis des verwendeten Speichers steigt von etwa 2-mal bei 50.000 auf 4-mal bei 1,5 Millionen, während das TOC-Verhältnis von etwa 30-mal bei 50.000 auf 25-mal bei 1,5 Millionen sinkt.

Fazit

Die Änderungen bezüglich des Dumpings großer Objekte in Postgres 17 sind sehr willkommen für Benutzer mit einer großen Anzahl großer Objekte. Der Speicherbedarf ist unter PostgreSQL 17 im Vergleich zu früheren Versionen wesentlich geringer, sowohl für Dumps im Custom- als auch im Directory-Format.

Leider ändern sich weder die Anzahl der Dateien im Verzeichnis noch die Verzeichnisgröße wesentlich, jedes große Objekt wird immer noch als eigene Datei gedumpt, was zu Problemen führen kann, wenn es viele Dateien gibt:

$ for version in 16 17; do echo -n "$version: "; find lo_test_$version.dir/ | wc -l; done
16: 1000003
17: 1001002
$ du -s -h lo_test_??.dir
4,1G    lo_test_16.dir
3,9G    lo_test_17.dir

Dies könnte ein Bereich für zukünftige Verbesserungen in Postgres 18 und darüber hinaus sein.

Kategorien: PostgreSQL®
Tags: planetpostgresql postgresql17

über den Autor

Michael Banck

zur Person

Michael Banck ist seit 2009 Mitarbeiter der credativ GmbH, sowie seit 2001 Mitglied des Debian Projekts und auch in weiteren Open Source Projekten aktiv. Als Mitglied des Datenbank-Teams von credativ hat er in den letzten Jahren verschiedene Kunden bei der Lösung von Problemen mit und dem täglichen Betrieb von PostgreSQL®, sowie bei der Einführung von Hochverfügbarkeits-Lösungen im Bereich Datenbanken unterstützt und beraten.

Beiträge ansehen


Beitrag teilen: