Setelah memahami RPO dan RTO di chapter sebelumnya, kita mulai dari keluarga backup yang paling banyak dipakai di dunia PostgreSQL: backup logis. Pendekatannya seperti menulis resep, bukan mengambil foto masakan — alat backup membaca struktur tabel, index, dan baris data, lalu menuliskannya kembali sebagai pernyataan SQL yang bisa dijalankan ulang di server mana pun. File hasilnya bisa dibuka dengan editor teks, diperiksa isinya, dipindahkan lintas versi PostgreSQL, bahkan diedit sebelum dipulihkan. Chapter ini membedah konsepnya: apa yang masuk dan apa yang tertinggal, bagaimana proses ekspor dan pemulihannya bekerja, serta tiga format penyimpanan yang tersedia.

Backup Logis Itu Sebenarnya Apa #

Backup logis mengekspor isi database — definisi objek beserta datanya — menjadi satu berkas yang bisa dibaca manusia atau format arsip terstruktur. pg_dump membaca katalog database (daftar tabel, kolom, tipe, constraint, index, view, function, trigger), lalu menuliskan urutan pernyataan CREATE TABLE, ALTER TABLE, COPY, dan sejenisnya yang jika dijalankan ulang akan membangunkan skema dan data yang sama persis di database tujuan.

Konsekuensi dari pendekatan “resep” ini ada dua sisi. Sisi baiknya: hasilnya portabel — dump dari PostgreSQL di Linux bisa dipulihkan di Windows atau macOS, dari versi 17 ke versi yang lebih baru, karena SQL adalah bahasa yang sama di mana-mana. Sisi beratnya: pemulihan harus memutar ulang seluruh SQL itu satu per satu, termasuk membuat ulang tiap index baris demi baris — jauh lebih lambat daripada sekadar menyalin file untuk database berukuran besar.

Ruang Lingkup: Satu Database atau Seluruh Cluster #

Chapter Konsep Backup menutup dengan pengingat bahwa cluster berisi tiga kelompok harta benda. Backup logis menyentuhnya dengan dua alat yang ruang lingkupnya berbeda:

AlatCakupanYang ikutYang tertinggal
pg_dumpsatu databasetabel, data, index, sequence, view, function, triggerrole, database lain, tablespace
pg_dumpallseluruh clustersemua isi tiap database + role + tablespace + definisi database—

Baris pertama tabel itu sengaja ditebalkan dalam ingatan Anda: pg_dump tidak menyertakan role, tidak menyertakan pernyataan CREATE DATABASE, dan tidak menyentuh database lain di cluster yang sama. Dump satu database yang sempurna sekalipun tidak bisa membangunkan cluster dari nol — kata sandi akun, izin akses, dan daftar database harus datang dari tempat lain. pg_dumpall hadir persisi untuk menutup lubang itu; chapter pg_dumpall membahasnya khusus.

Praktik: Mengekspor dan Melihat Isinya #

Mari buktikan sifat “teks SQL” itu langsung. File pertama menyiapkan dua tabel uji Toko Maju Jaya dengan data tetap:

sql siapkan-uji.sql
-- Dua tabel uji Toko Maju Jaya untuk mengamati backup logis.
CREATE TABLE produk (
  id integer PRIMARY KEY,
  nama varchar(40),
  harga numeric(10,2)
);

INSERT INTO produk (id, nama, harga) VALUES
  (1, 'Kopi Gayo 250g', 45000),
  (2, 'Teh Melati 100g', 15000),
  (3, 'Gula Aren 500g', 32000);

CREATE TABLE penjualan (
  id integer PRIMARY KEY,
  produk_id integer REFERENCES produk(id),
  qty integer
);

INSERT INTO penjualan (id, produk_id, qty) VALUES
  (101, 1, 2),
  (102, 3, 1),
  (103, 2, 5);

SELECT (SELECT count(*) FROM produk) AS jumlah_produk,
       (SELECT count(*) FROM penjualan) AS jumlah_penjualan;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 3
CREATE TABLE
INSERT 0 3
 jumlah_produk | jumlah_penjualan 
---------------+------------------
             3 |                3
(1 row)

Skrip berikut menjalankan pg_dump tanpa opsi apa pun — bentuk paling dasarnya — lalu menelusuri isi file hasilnya. Seperti biasa di course ini, psql polos dan pg_dump polos mengarah ke database yang ditunjuk variabel lingkungan PGDATABASE:

bash ekspor-logis.sh
#!/bin/bash
# Backup logis: pg_dump mengekspor objek dan data sebagai teks SQL.
pg_dump "$PGDATABASE" > backup_logis.sql

# Ukuran hasil ekspor: jumlah baris dan jumlah byte.
wc -l backup_logis.sql
wc -c backup_logis.sql

# Bukti isinya SQL: perintah CREATE TABLE dan COPY untuk tiap tabel.
grep -E '^(CREATE TABLE|COPY )' backup_logis.sql

# Yang TIDAK ikut di dalamnya: role dan database itu sendiri.
echo "jumlah CREATE ROLE    : $(grep -c 'CREATE ROLE' backup_logis.sql)"
echo "jumlah CREATE DATABASE: $(grep -c 'CREATE DATABASE' backup_logis.sql)"

Output yang Dihasilkan:

text
103 backup_logis.sql
2218 backup_logis.sql
CREATE TABLE public.penjualan (
CREATE TABLE public.produk (
COPY public.penjualan (id, produk_id, qty) FROM stdin;
COPY public.produk (id, nama, harga) FROM stdin;
jumlah CREATE ROLE    : 0
jumlah CREATE DATABASE: 0

Empat pengamatan penting dari keluaran itu:

  1. wc -l dan wc -c: file berukuran 103 baris dan 2218 byte — angka yang tetap sama untuk data yang sama, karena isi dump ditulis dengan urutan baku. Dua baris SET hingga SET row_security di bagian pembuka menyiapkan sesi psql agar restore berperilaku seperti saat dump dibuat.
  2. grep -E '^(CREATE TABLE|COPY )': terlihat CREATE TABLE public.penjualan ( dan CREATE TABLE public.produk ( — objek ditulis dengan nama skema lengkap — disusul COPY public.penjualan (id, produk_id, qty) FROM stdin; untuk tiap tabel berisi data. Inilah wujud backup logis: skema dan data sebagai SQL.
  3. Baris COPY ... FROM stdin: data baris tidak ditulis sebagai INSERT satu per satu, melainkan dalam format COPY yang jauh lebih ringkas dan cepat diputar ulang.
  4. Dua baris jumlah CREATE ...: keduanya 0. Tidak ada satu pun CREATE ROLE atau CREATE DATABASE di file ini — bukti nyata bahwa dump satu database tidak memindahkan penghuni cluster. (Sejak PostgreSQL 17.6, dump juga menyisipkan baris \restrict berisi token acak di awal dan akhir file sebagai pengaman keamanan psql — dua dump dari data yang sama karenanya tidak pernah identik byte demi byte, meski isinya selalu sama.)

Pemulihan: Memutar Ulang SQL #

Sisi lain dari backup logis terlihat saat memulihkannya: tidak ada sihir — psql menjalankan file dump sebagai skrip SQL biasa di database tujuan yang kosong:

#!/bin/bash
# Pemulihan backup logis: putar ulang seluruh file SQL ke database kosong.
psql -X -d postgres -v ON_ERROR_STOP=1 \
  -c "DROP DATABASE IF EXISTS pulih_logis WITH (FORCE)" \
  -c "CREATE DATABASE pulih_logis" > /dev/null

# psql menjalankan file dump sebagai skrip SQL biasa;
# kita saring keluarannya agar hanya tag penting yang terlihat.
psql -X -d pulih_logis -v ON_ERROR_STOP=1 -f backup_logis.sql \
  | grep -E '^(CREATE TABLE|COPY )'

# Data kembali utuh di database yang baru.
psql -X -d pulih_logis -c "SELECT id, nama, harga FROM produk ORDER BY id"

Output yang Dihasilkan:

text
CREATE TABLE
CREATE TABLE
COPY 3
COPY 3
 id |      nama       |  harga   
----+-----------------+----------
  1 | Kopi Gayo 250g  | 45000.00
  2 | Teh Melati 100g | 15000.00
  3 | Gula Aren 500g  | 32000.00
(3 rows)

Tabel dibangunkan oleh CREATE TABLE, data diisikan oleh COPY 3, dan verifikasi akhir menunjukkan tiga produk kembali utuh di pulih_logis. Perhatikan juga pola skripnya: DROP DATABASE IF EXISTS ... WITH (FORCE) di awal membuat skrip aman dijalankan berulang, dan -v ON_ERROR_STOP=1 memastikan kegagalan di tengah jalan langsung menghentikan eksekusi alih-alih meninggalkan restore setengah jadi.

Format Penyimpanan: plain, custom, dan directory #

pg_dump menulis hasilnya dalam satu dari tiga format, dipilih lewat opsi -F:

FormatOpsiWujud fileAlat pemulihKompresiSelektif saat restore
plain-Fp (bawaan)satu file teks .sqlpsqltidaksulit — harus diedit manual
custom-Fcsatu file biner terkompresipg_restoreya, bawaan gzipya — --table, --list
directory-Fdsatu folder berisi file per objekpg_restoreya, per objekya — --table, --list

Format plain adalah yang kita pakai di dua contoh di atas: transparan dan portabel maksimal. Format custom mengemas skema dan data dalam satu file terkompresi yang lebih kecil, lebih cepat dipulihkan, dan — karena menyimpan daftar isi — memungkinkan pemulihan selektif per tabel lewat pg_restore. Format directory menambah kemampuan lagi: tiap tabel ditulis sebagai file sendiri sehingga dump maupun restore bisa dijalankan paralel dengan --jobs. Chapter pg_dump dan pg_restore membedah format kedua dan ketiga ini sampai ke perintahnya.

Portabilitas: Kekuatan Terbesar Backup Logis #

Satu keunggulan backup logis yang tidak dimiliki keluarga fisik: lintas versi dan arsitektur. Dump teks dari PostgreSQL 17 bisa dipulihkan ke PostgreSQL 18 tanpa proses konversi; dump dari server Linux x86-64 bisa dipulihkan ke server ARM. Proses peningkatan versi major yang memakai pg_dump/pg_restore (pg_upgrade --clone di luar itu) bergantung pada jalan ini. Sebaliknya, keluarga backup fisik menuntut versi major yang sama persis — topik yang dibedah tuntas di chapter Physical Backup .

Backup logis kini punya wajah utuh di tangan Anda: ekspor sebagai SQL, pemulihan sebagai pemutar-ulang SQL, cakupan per database, dan tiga format penyimpanan. Chapter berikutnya membawa alat utamanya ke meja operasi — pg_dump, mulai dari pemakaian paling sederhana sampai penyaringan per tabel dan pemeriksaan exit code untuk skrip cron.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

backup_logis.sql

Unduh berkas
sql
--
-- PostgreSQL database dump
--

\restrict fRFoemq1JHqPlA3DMp6plSgVp4zxr57h4JLRdyMhIrdLz2Wwi1x0chYRbCQWhhW

-- Dumped from database version 17.11 (Debian 17.11-0+deb13u1)
-- Dumped by pg_dump version 17.11 (Debian 17.11-0+deb13u1)

SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET transaction_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;

SET default_tablespace = '';

SET default_table_access_method = heap;

--
-- Name: penjualan; Type: TABLE; Schema: public; Owner: postgres
--

CREATE TABLE public.penjualan (
    id integer NOT NULL,
    produk_id integer,
    qty integer
);


ALTER TABLE public.penjualan OWNER TO postgres;

--
-- Name: produk; Type: TABLE; Schema: public; Owner: postgres
--

CREATE TABLE public.produk (
    id integer NOT NULL,
    nama character varying(40),
    harga numeric(10,2)
);


ALTER TABLE public.produk OWNER TO postgres;

--
-- Data for Name: penjualan; Type: TABLE DATA; Schema: public; Owner: postgres
--

COPY public.penjualan (id, produk_id, qty) FROM stdin;
101	1	2
102	3	1
103	2	5
\.


--
-- Data for Name: produk; Type: TABLE DATA; Schema: public; Owner: postgres
--

COPY public.produk (id, nama, harga) FROM stdin;
1	Kopi Gayo 250g	45000.00
2	Teh Melati 100g	15000.00
3	Gula Aren 500g	32000.00
\.


--
-- Name: penjualan penjualan_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--

ALTER TABLE ONLY public.penjualan
    ADD CONSTRAINT penjualan_pkey PRIMARY KEY (id);


--
-- Name: produk produk_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--

ALTER TABLE ONLY public.produk
    ADD CONSTRAINT produk_pkey PRIMARY KEY (id);


--
-- Name: penjualan penjualan_produk_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--

ALTER TABLE ONLY public.penjualan
    ADD CONSTRAINT penjualan_produk_id_fkey FOREIGN KEY (produk_id) REFERENCES public.produk(id);


--
-- PostgreSQL database dump complete
--

\unrestrict fRFoemq1JHqPlA3DMp6plSgVp4zxr57h4JLRdyMhIrdLz2Wwi1x0chYRbCQWhhW

ekspor-logis.sh

Unduh berkas
bash
#!/bin/bash
# Backup logis: pg_dump mengekspor objek dan data sebagai teks SQL.
pg_dump "$PGDATABASE" > backup_logis.sql

# Ukuran hasil ekspor: jumlah baris dan jumlah byte.
wc -l backup_logis.sql
wc -c backup_logis.sql

# Bukti isinya SQL: perintah CREATE TABLE dan COPY untuk tiap tabel.
grep -E '^(CREATE TABLE|COPY )' backup_logis.sql

# Yang TIDAK ikut di dalamnya: role dan database itu sendiri.
echo "jumlah CREATE ROLE    : $(grep -c 'CREATE ROLE' backup_logis.sql)"
echo "jumlah CREATE DATABASE: $(grep -c 'CREATE DATABASE' backup_logis.sql)"

pulihkan-logis.sh

Unduh berkas
bash
#!/bin/bash
# Pemulihan backup logis: putar ulang seluruh file SQL ke database kosong.
psql -X -d postgres -v ON_ERROR_STOP=1 \
  -c "DROP DATABASE IF EXISTS pulih_logis WITH (FORCE)" \
  -c "CREATE DATABASE pulih_logis" > /dev/null

# psql menjalankan file dump sebagai skrip SQL biasa;
# kita saring keluarannya agar hanya tag penting yang terlihat.
psql -X -d pulih_logis -v ON_ERROR_STOP=1 -f backup_logis.sql \
  | grep -E '^(CREATE TABLE|COPY )'

# Data kembali utuh di database yang baru.
psql -X -d pulih_logis -c "SELECT id, nama, harga FROM produk ORDER BY id"

siapkan-uji.sql

Unduh berkas
sql
-- Dua tabel uji Toko Maju Jaya untuk mengamati backup logis.
CREATE TABLE produk (
  id integer PRIMARY KEY,
  nama varchar(40),
  harga numeric(10,2)
);

INSERT INTO produk (id, nama, harga) VALUES
  (1, 'Kopi Gayo 250g', 45000),
  (2, 'Teh Melati 100g', 15000),
  (3, 'Gula Aren 500g', 32000);

CREATE TABLE penjualan (
  id integer PRIMARY KEY,
  produk_id integer REFERENCES produk(id),
  qty integer
);

INSERT INTO penjualan (id, produk_id, qty) VALUES
  (101, 1, 2),
  (102, 3, 1),
  (103, 2, 5);

SELECT (SELECT count(*) FROM produk) AS jumlah_produk,
       (SELECT count(*) FROM penjualan) AS jumlah_penjualan;