pg_prewarm - Turbostart für den Postgres Buffer Cache

Database

pg_prewarm ist eine Extension zum Laden von Tabellenblöcken in den Betriebssystem oder PostgreSQL Buffer Cache. Dabei wird die Ausführung entweder manuell über das Ausführen einer Funktion oder automatisch per Konfigurationseintrag in der postgresql.conf mit dem Parameter shared_preload_libraries gesteuert. Im zweiten Fall läuft ein worker Prozess bei der PostgreSQL Instanz mit und speichert regelmäßig den Inhalt der Shared Buffers in der Datei $PGDATA/autoprewarm.blocks. Im Falle eines Neustarts der Instanz laden zwei background worker alle in der Datei hinterlegten Blöcke erneut in die Shared Buffers.

Die Extension enthält noch zwei Konfigurationsparameter für die pospg_prewarm.autoprewarm (boolean): Ermöglicht das das An-/Abschalten von pg_prewarm ohne das Einträge aus der postgresql.conf entfernt werden müssen. Der Standardwert ist “on” und der Parameter kann nur beim Serverstart gesetzt werden.

  • pg_prewarm.autoprewarm_interval (int): Intervall der Updates der autoprewarm.blocks Datei in Sekunden. Der Standardwert sind 300 Sekunden. Beim Einstellen des Werts 0 wird nur beim Herunterfahren der PostgreSQL Instanz ein Dump erzeugt.

Wenn die Extension im laufenden Betrieb des Datenbankclusters aktiviert wurde, bieten die Funktionen autoprewarm_start_worker(), das nachträgliche Starten des background worker Prozesses, sowie autoprewarm_dump_now() das Aktualisieren der autoprewarm.blocks Datei.

Zuletzt kann die Aufnahme von Tabellen und deren Blöcken auch über die Funktion pg_prewarm(...) gesteuert werden:

 

pg_prewarm(regclass,
           mode text default 'buffer',
           fork text default 'main',
           first_block int8 default null,
           last_block int8 default null)
  • regclass: Name der Tabelle
  • mode: Es gibt 3 unterschiedliche Ladestrategien

- prefetch: Asynchrones Laden per Betriebssystemaufruf

- read: Liest die Liste der definierten Datenblöcke und lädt im Gegensatz zu prefetch synchron, aber eventuell langsamer

- buffer: Liest die Datenblöcke in den Datenbank Buffer Cache

  • fork: Normalerweise main für die Hauptdatendatei
  • first_block: Erster zu lesender Block, NULL synonym für 0, also Beginn der Tabelle
  • last_block: Letzter zu lesender Block, NULL bis Ende der Tabelle

Anmerkung: Mehr Informationen zu dem Parameter fork und seiner Bedeutung ist in der Postgres Dokumentation zu finden.

Zum Testen wird docker-compose mit Postgres v12 verwendet. In einem ersten Test wird dabei pg_prewarm nicht verwendet.

 

version: "3.8"
services:
  db:
    image: "postgres:latest"
    container_name: "pg12"
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    ports:
      - "5432:5432"
    command:
      - "postgres"
      - "-c"
      - "shared_buffers=4GB"
    volumes:
      - pg12_data:/var/lib/postgresql/data
volumes:
  pg12_data:

 

Der Container wird mit docker-compose gestartet und danach wird sich per docker exec auf den Container geschaltet.

 

docker-compose up -d

docker exec -it pg12 bash

 

Nun wird eine Datenbank testdb erzeugt und pgbench initialisiert. Dabei ist die Skalierung so gewählt, dass die Datenbank komplett in die Shared Buffers geladen werden kann.

 

psql -U postgres -c "CREATE DATABASE testdb;"

CREATE DATABASE

pgbench -U postgres -i -s 250 testdb

dropping old tables...
creating tables...
generating data...
100000 of 20000000 tuples (0%) done (elapsed 0.06 s, remaining 11.84 s)
200000 of 20000000 tuples (1%) done (elapsed 0.18 s, remaining 17.48 s)
...
19900000 of 20000000 tuples (99%) done (elapsed 19.24 s, remaining 0.10 s)
20000000 of 20000000 tuples (100%) done (elapsed 19.34 s, remaining 0.00 s)
vacuuming...
creating primary keys...
done.

psql -U postgres -d testdb -c "\l+ testdb"

                                                List of databases
  Name  |  Owner   | Encoding |  Collate   |   Ctype    | Access privileges |  Size   | Tablespace | Description 
--------+----------+----------+------------+------------+-------------------+---------+------------+-------------
 testdb | postgres | UTF8     | en_US.utf8 | en_US.utf8 |                   | 3746 MB | pg_default | 
(1 row)

 

Der eigentliche Test kann jetzt beginnen. Dazu wird der Container gestoppt, erneut gestartet und in den Container verbunden.

 

docker-compose down

docker-compose up -d

docker exec -it pg12 bash

 

Per pgbench werden die TPS ermittelt. Es wird ein Client simuliert, der 10 Minuten lang nur selects ausführt. pgbench gibt den Fortschritt inklusive TPS-Wert alle 5 Sekunden aus. Dieser wird in die Logdatei pgbench.log geschrieben.

 

pgbench -U postgres -c 1 -S -T 600 -P 5 testdb > pgbench.log 2>&1

 

Als nächstes wird die docker-compose Datei um pg_prewarm als Startparameter erweitert.

 

version: "3.8"
services:
  db:
    image: "postgres:latest"
    container_name: "pg12_with_prewarm"
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    ports:
      - "5432:5432"
    command:
      - "postgres"
      - "-c"
      - "shared_buffers=4GB"
      - "-c"
      - "shared_preload_libraries=pg_prewarm"
    volumes:
      - pg12_prewarm_data:/var/lib/postgresql/data
volumes:
  pg12_prewarm_data:

 

Der Container wird gestartet.

 

docker-compose up -d

docker exec -it pg12_with_prewarm bash

 

Als erstes lässt sich der zu Beginn beschriebene Prozess zu pg_prewarm anzeigen.

 

ps -aef | grep autoprewarm

postgres    31     1  0 08:02 ?        00:00:00 postgres: autoprewarm master

 

Wie bei dem ersten Container wird die Datenbank testdb erzeugt und danach pg_prewarm aktiviert. Die pgbench Tabellen werden explizit geladen und anschließend mit autoprewarm_dump_now gedumpt.

 

psql -U postgres -d testdb -c "CREATE EXTENSION pg_prewarm;"

CREATE EXTENSION

psql -U postgres -d testdb -c "SELECT * FROM pg_prewarm('public.pgbench_accounts');"

 pg_prewarm 
------------
     409837
(1 row)

psql -U postgres -d testdb -c "SELECT * FROM pg_prewarm('public.pgbench_branches');"

 pg_prewarm 
------------
          2
(1 row)

psql -U postgres -d testdb -c "SELECT * FROM pg_prewarm('public.pgbench_tellers');"

 pg_prewarm 
------------
         14
(1 row)

psql -U postgres -d testdb -c "SELECT * FROM autoprewarm_dump_now();"

 autoprewarm_dump_now 
----------------------
               410382
(1 row)

 

Der eigentliche Test kann jetzt beginnen. Dazu wird der Container gestoppt, erneut gestartet und mit dem Container verbunden.

 

docker-compose down

docker-compose up -d

docker exec -it pg12_with_prewarm bash
Per pgbench werden wieder die TPS Werte ermittelt.
pgbench -U postgres -c 1 -S -T 600 -P 5 testdb > pgbench.log 2>&1

Abbildung: Vergleich der TPS Werte

Beim Vergleich der TPS Werte ohne und mit pg_prewarm zeigen sich zu Beginn der Aufzeichnungen deutliche Unterschiede. Ohne pg_prewarm dauert es fast 2 Minuten lang bis die TPS Werte ein ähnliches Niveau wie mit pg_prewarm erreichen. Das liegt daran, dass die Data Pages erst bei Abfrage aus dem Dateisystem in den PostgreSQL Buffer Cache geladen werden. Daraus ist zu schließen, dass pg_prewarm bei (sehr) großen Datenbankclustern mit viel RAM einen deutlichen Vorteil bringt.

Die Extension hilft so bei unterschiedlichsten Anwendungsfällen. Dazu gehört an erster Stelle die bessere Reaktionszeit auf User-Anfragen nach einem Neustart des Datenbankclusters. Auf der anderen Seite kann pg_prewarm auch bei Tests genutzt werden, um authentische Zustände im Buffer Cache zu generieren, wie sie auch in Produktion zu finden sind.

Dirk Aumueller Autor

Dirk Aumueller arbeitet als Associate Partner für die Proventa AG. Sein technologischer Schwerpunkt liegt bei Datenbankarchitekturen mit PostgreSQL sowie Data Management Lösungen mit Pentaho. Zusätzlich zu seinen Datenbanktransformations-Projekteinsätzen ist er regelmäßig als PostgreSQL Trainer unterwegs und betreut Studenten bei ihren Abschlussarbeiten. Seine fachlichen Erfahrungen erstrecken sich über die Branchen Telco und Financial Services.

Tags