Satu server database adalah satu titik kegagalan. Ketika server itu mati, seluruh aplikasi ikut mati, dan waktu pemulihan bergantung sepenuhnya pada seberapa baru backup terakhir Anda.
Replikasi menyelesaikan separuh masalah tersebut dengan menyediakan salinan data yang selalu mengikuti server utama. Separuh sisanya, yaitu proses pengalihan otomatis ketika server utama tumbang, membutuhkan lapisan orkestrasi tambahan.
Artikel ini membahas keduanya secara berurutan: streaming replication manual antara dua node, lalu peningkatannya menjadi cluster high availability dengan failover otomatis menggunakan Patroni, etcd, dan HAProxy di atas Ubuntu Server 24.04 LTS.
Catatan Istilah
Istilah master-slave masih umum dipakai di lapangan, tetapi dokumentasi resmi PostgreSQL menggunakan istilah primary dan standby. Artikel ini memakai istilah primary dan standby agar konsisten dengan nama parameter konfigurasi dan pesan log yang akan Anda temui.
Tujuan Tutorial
Setelah menyelesaikan panduan ini, Anda akan memiliki:
Streaming replication asynchronous antara satu primary dan satu standby.
Pemahaman tentang replication slot dan cara mencegah WAL terhapus sebelum standby sempat membacanya.
Prosedur failover manual dan cara mengembalikan bekas primary menjadi standby dengan
pg_rewind.Cluster tiga node dengan failover otomatis menggunakan Patroni dan etcd.
Satu endpoint tunggal untuk aplikasi melalui HAProxy, sehingga aplikasi tidak perlu tahu node mana yang sedang menjadi primary.
Prasyarat
Tiga server Ubuntu Server 24.04 LTS untuk bagian high availability. Bagian replikasi dasar hanya membutuhkan dua server.
Akses
sudopada seluruh server.Jaringan privat antar server dengan latensi rendah.
Pemahaman dasar tentang PostgreSQL,
systemd, dan konfigurasi firewall.
Panduan ini ditujukan untuk pembaca tingkat menengah. Konsep dasar seperti tabel, koneksi, dan role diasumsikan sudah dipahami.
Topologi
Bagian pertama membangun replikasi dua node:
+-------------------+ WAL stream +-------------------+
| pg-node-1 | ---------------------------> | pg-node-2 |
| 10.10.0.11 | | 10.10.0.12 |
| PRIMARY | | STANDBY (read) |
+-------------------+ +-------------------+
Bagian kedua mengembangkannya menjadi cluster HA tiga node:
+-----------------+
aplikasi -----> | HAProxy |
| port 5000 RW |
| port 5001 RO |
+-----------------+
|
+------------------------+------------------------+
| | |
+-------------+ +-------------+ +-------------+
| pg-node-1 | | pg-node-2 | | pg-node-3 |
| Patroni | | Patroni | | Patroni |
| PostgreSQL | | PostgreSQL | | PostgreSQL |
| etcd | | etcd | | etcd |
+-------------+ +-------------+ +-------------+
Jumlah node etcd sengaja ganjil. Cluster etcd membutuhkan kuorum mayoritas untuk mengambil keputusan, sehingga tiga node mentoleransi kegagalan satu node. Dua node etcd justru lebih rapuh daripada satu node, karena kegagalan satu node langsung menghilangkan kuorum.
Persiapan Semua Server
Langkah pada bagian ini dijalankan di seluruh node.
Terminal
sudo apt update
sudo apt upgrade -y
sudo apt install -y curl gnupg ca-certificates
/etc/hosts
10.10.0.11 pg-node-1
10.10.0.12 pg-node-2
10.10.0.13 pg-node-3
Resolusi nama melalui /etc/hosts menghilangkan ketergantungan pada DNS. Ketika DNS bermasalah, cluster database sebaiknya tetap bisa saling mengenali.
Sesuaikan hostname pada masing-masing server:
Terminal
sudo hostnamectl set-hostname pg-node-1
Instalasi PostgreSQL
Ubuntu 24.04 menyediakan PostgreSQL 16 pada repositori resminya. Untuk mendapatkan versi mayor yang lebih baru, gunakan repositori PGDG.
Terminal
sudo apt install -y postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
sudo apt install -y postgresql-16 postgresql-client-16
Skrip apt.postgresql.org.sh menambahkan repositori PGDG beserta kuncinya secara otomatis. Flag -y membuatnya berjalan tanpa konfirmasi interaktif.
Verifikasi hasil instalasi:
Command
sudo -u postgres psql -c "SELECT version();"
Output yang diharapkan
version
---------------------------------------------------------------------------------------
PostgreSQL 16.x (Ubuntu 16.x-1.pgdg24.04+1) on x86_64-pc-linux-gnu, compiled by gcc...
(1 row)
Nomor versi minor bergantung pada waktu instalasi Anda.
Struktur direktori penting pada instalasi berbasis paket Debian dan Ubuntu:
/etc/postgresql/16/main/
├── postgresql.conf
├── pg_hba.conf
└── conf.d/
/var/lib/postgresql/16/main/ <- direktori data
/var/log/postgresql/ <- log server
Perhatikan bahwa berkas konfigurasi terpisah dari direktori data. Ini berbeda dari instalasi hasil kompilasi sendiri, di mana postgresql.conf berada di dalam direktori data.
Bagian 1: Streaming Replication Manual
Konfigurasi Node Primary
Semua perintah pada bagian ini dijalankan di pg-node-1.
Buat role khusus replikasi terlebih dahulu:
Command
sudo -u postgres psql -c "CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'ganti_password_kuat';"
Role ini hanya memiliki atribut REPLICATION dan LOGIN, tanpa hak superuser. Ini adalah praktik yang benar, karena replikasi tidak membutuhkan hak akses penuh.
Selanjutnya buat berkas konfigurasi khusus replikasi di direktori conf.d agar tidak mengubah postgresql.conf bawaan.
/etc/postgresql/16/main/conf.d/replication.conf
listen_addresses = '*'
port = 5432
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
wal_keep_size = '1GB'
wal_log_hints = on
hot_standby = on
hot_standby_feedback = on
archive_mode = on
archive_command = 'test ! -f /var/lib/postgresql/wal_archive/%f && cp %p /var/lib/postgresql/wal_archive/%f'
Parameter wal_level = replica mengaktifkan pencatatan WAL yang cukup detail untuk replikasi fisik. Nilai max_wal_senders menentukan jumlah maksimum koneksi streaming yang dilayani secara bersamaan.
Parameter wal_log_hints = on wajib diaktifkan sejak awal jika Anda berencana menggunakan pg_rewind. Mengaktifkannya belakangan membutuhkan restart, dan pg_rewind tidak akan bisa dipakai untuk data yang sudah terlanjur ditulis tanpa parameter ini. Alternatifnya adalah mengaktifkan data checksums saat initdb.
Parameter hot_standby_feedback = on membuat standby memberi tahu primary tentang query yang sedang berjalan, sehingga primary menunda proses vacuum yang berpotensi membatalkan query tersebut. Efek sampingnya, bloat pada primary bisa meningkat jika standby menjalankan query yang sangat lama.
Parameter archive_command di atas sengaja ditulis sederhana untuk arsip lokal. Pada lingkungan produksi, gunakan tool khusus seperti pgBackRest atau WAL-G yang menangani verifikasi, retensi, dan penyimpanan objek secara benar.
Siapkan direktori arsip:
Terminal
sudo mkdir -p /var/lib/postgresql/wal_archive
sudo chown postgres:postgres /var/lib/postgresql/wal_archive
sudo chmod 700 /var/lib/postgresql/wal_archive
Tambahkan aturan otentikasi untuk koneksi replikasi.
/etc/postgresql/16/main/pg_hba.conf
host replication replicator 10.10.0.12/32 scram-sha-256
host replication replicator 10.10.0.13/32 scram-sha-256
host all all 10.10.0.0/24 scram-sha-256
Baris dengan database bernama replication bukan nama database sungguhan, melainkan kata kunci khusus untuk koneksi streaming replication. Metode scram-sha-256 adalah standar saat ini dan jauh lebih aman daripada md5.
Terapkan konfigurasi:
Terminal
sudo systemctl restart postgresql@16-main
sudo systemctl status postgresql@16-main --no-pager
Buat replication slot agar primary menyimpan WAL sampai standby benar-benar membacanya:
Command
sudo -u postgres psql -c "SELECT pg_create_physical_replication_slot('standby_node2');"
Output yang diharapkan
pg_create_physical_replication_slot
-------------------------------------
(standby_node2,)
(1 row)
Replication slot mencegah kondisi di mana standby yang sempat terputus lama kehilangan WAL yang sudah didaur ulang oleh primary. Risikonya perlu dipahami: jika sebuah standby mati permanen dan slot-nya tidak dihapus, WAL akan menumpuk sampai disk primary penuh. Slot yang tidak terpakai harus dihapus dengan pg_drop_replication_slot().
Konfigurasi Node Standby
Perintah pada bagian ini dijalankan di pg-node-2.
Instalasi paket menghasilkan cluster kosong yang harus dihapus lebih dulu, karena pg_basebackup membutuhkan direktori data yang kosong.
Terminal
sudo systemctl stop postgresql@16-main
sudo -u postgres rm -rf /var/lib/postgresql/16/main
Perintah di atas menghapus data secara permanen. Pastikan Anda benar-benar berada di server standby sebelum menjalankannya.
Salin data awal dari primary:
Terminal
sudo -u postgres PGPASSWORD='ganti_password_kuat' pg_basebackup \
--host=10.10.0.11 \
--username=replicator \
--pgdata=/var/lib/postgresql/16/main \
--wal-method=stream \
--slot=standby_node2 \
--write-recovery-conf \
--checkpoint=fast \
--progress
Opsi --write-recovery-conf membuat pg_basebackup menuliskan primary_conninfo ke dalam postgresql.auto.conf sekaligus membuat berkas penanda standby.signal. Keberadaan berkas standby.signal di direktori data inilah yang membuat PostgreSQL memulai dirinya dalam mode standby.
Opsi --wal-method=stream membuka koneksi kedua untuk mengalirkan WAL selama proses penyalinan berlangsung, sehingga basebackup pada database besar tidak gagal karena WAL yang dibutuhkan sudah terlanjur hilang.
Opsi --slot=standby_node2 mengaitkan standby ini ke slot yang sudah dibuat sebelumnya.
Perbaiki permission dan jalankan layanan:
Terminal
sudo chmod 700 /var/lib/postgresql/16/main
sudo systemctl start postgresql@16-main
PostgreSQL menolak dijalankan jika permission direktori data lebih longgar daripada 0700.
Verifikasi Replikasi
Jalankan perintah berikut di primary:
Command
sudo -u postgres psql -x -c "SELECT client_addr, state, sync_state, sent_lsn, replay_lsn FROM pg_stat_replication;"
Output yang diharapkan
-[ RECORD 1 ]---------------
client_addr | 10.10.0.12
state | streaming
sync_state | async
sent_lsn | 0/4000148
replay_lsn | 0/4000148
Kolom state bernilai streaming menandakan replikasi berjalan normal. Nilai sent_lsn dan replay_lsn yang identik menandakan standby sudah menyusul sepenuhnya.
Verifikasi di standby:
Command
sudo -u postgres psql -c "SELECT pg_is_in_recovery();"
Output yang diharapkan
pg_is_in_recovery
-------------------
t
(1 row)
Nilai t menegaskan node berjalan sebagai standby. Uji alur data secara langsung dengan membuat tabel di primary lalu membacanya di standby:
Command
sudo -u postgres psql -c "CREATE TABLE uji_replikasi AS SELECT now() AS dibuat_pada;"
Kemudian di standby:
Command
sudo -u postgres psql -c "SELECT * FROM uji_replikasi;"
Percobaan menulis di standby akan ditolak dengan pesan cannot execute CREATE TABLE in a read-only transaction. Ini adalah perilaku yang benar, bukan error konfigurasi.
Untuk memantau keterlambatan replikasi, jalankan di standby:
Command
sudo -u postgres psql -c "SELECT now() - pg_last_xact_replay_timestamp() AS lag;"
Perlu diperhatikan bahwa nilai ini akan terus bertambah ketika primary sedang tidak menerima transaksi sama sekali, karena tidak ada transaksi baru yang di-replay. Kondisi tersebut normal dan bukan indikasi replikasi terputus.
Replikasi Synchronous
Konfigurasi di atas bersifat asynchronous. Primary tidak menunggu standby, sehingga throughput tinggi tetapi transaksi terakhir berpotensi hilang saat primary mati mendadak.
Jika kehilangan data sama sekali tidak dapat ditoleransi, aktifkan mode synchronous di primary.
/etc/postgresql/16/main/conf.d/replication.conf
synchronous_commit = on
synchronous_standby_names = 'FIRST 1 (node2, node3)'
Nama pada synchronous_standby_names merujuk ke application_name yang dikirim standby melalui primary_conninfo. Pastikan nilainya diset di sisi standby.
/var/lib/postgresql/16/main/postgresql.auto.conf
primary_conninfo = 'host=10.10.0.11 port=5432 user=replicator password=ganti_password_kuat application_name=node2'
Konsekuensi mode synchronous harus dipahami sebelum diaktifkan. Setiap commit di primary menunggu konfirmasi dari standby, sehingga latensi jaringan langsung menjadi latensi transaksi. Lebih penting lagi, jika seluruh standby yang terdaftar tidak tersedia, primary akan menggantung menunggu konfirmasi yang tidak akan pernah datang. Karena itu, mode synchronous dengan hanya satu standby justru bisa menurunkan ketersediaan.
Failover Manual
Ketika primary benar-benar mati, promosikan standby:
Terminal
sudo -u postgres pg_ctlcluster 16 main promote
Alternatif melalui SQL:
Command
sudo -u postgres psql -c "SELECT pg_promote(wait => true);"
Setelah promosi, pg_is_in_recovery() di node tersebut akan bernilai f, berkas standby.signal terhapus otomatis, dan node siap menerima operasi tulis. Arahkan aplikasi ke alamat node baru.
Pastikan primary lama benar-benar mati sebelum melakukan promosi. Dua node yang sama-sama menerima tulisan akan menghasilkan split-brain, dan penggabungan datanya kembali adalah pekerjaan manual yang menyakitkan.
Mengembalikan Bekas Primary sebagai Standby
Bekas primary tidak bisa langsung dijadikan standby, karena timeline-nya sudah berbeda dari primary yang baru. Perintah pg_rewind menyelaraskan perbedaan tersebut tanpa perlu menyalin ulang seluruh database.
Terminal
sudo systemctl stop postgresql@16-main
sudo -u postgres pg_rewind \
--target-pgdata=/var/lib/postgresql/16/main \
--source-server="host=10.10.0.12 port=5432 user=postgres dbname=postgres" \
--progress
Perintah pg_rewind mensyaratkan wal_log_hints = on atau data checksums aktif pada target. Node target juga harus dimatikan secara bersih terlebih dahulu. Setelah pg_rewind selesai, buat berkas standby.signal dan atur primary_conninfo agar mengarah ke primary baru, lalu jalankan kembali layanannya.
Jika pg_rewind gagal, jalan keluar yang selalu berhasil adalah menghapus direktori data dan mengulang pg_basebackup dari primary baru.
Bagian 2: High Availability dengan Patroni
Failover manual berarti seseorang harus terjaga, menyadari adanya insiden, lalu masuk ke server. Patroni mengotomatiskan proses tersebut dengan menyimpan status cluster di penyimpanan konsensus terdistribusi, dalam hal ini etcd.
Prinsip kerjanya adalah leader election. Node yang berhasil memegang kunci leader di etcd menjalankan diri sebagai primary dan memperbarui kunci tersebut secara berkala. Jika pembaruan berhenti karena node mati, kunci kedaluwarsa, dan node lain yang memenuhi syarat akan mengambil alih.
Menyiapkan etcd
Jalankan pada ketiga node.
Terminal
sudo apt install -y etcd-server etcd-client
sudo systemctl stop etcd
Ketersediaan dan nomor versi paket etcd-server pada repositori Ubuntu 24.04 sebaiknya Anda periksa dengan apt policy etcd-server. []
/etc/default/etcd
ETCD_NAME="pg-node-1"
ETCD_DATA_DIR="/var/lib/etcd/default"
ETCD_LISTEN_PEER_URLS="http://10.10.0.11:2380"
ETCD_LISTEN_CLIENT_URLS="http://10.10.0.11:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.10.0.11:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://10.10.0.11:2379"
ETCD_INITIAL_CLUSTER="pg-node-1=http://10.10.0.11:2380,pg-node-2=http://10.10.0.12:2380,pg-node-3=http://10.10.0.13:2380"
ETCD_INITIAL_CLUSTER_TOKEN="pg-ha-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
Sesuaikan ETCD_NAME dan seluruh alamat IP pada setiap node. Nilai ETCD_INITIAL_CLUSTER harus identik di ketiga node.
Konfigurasi di atas menggunakan HTTP tanpa TLS demi kesederhanaan. Pada produksi, aktifkan TLS untuk peer maupun client, dan pastikan port 2379 serta 2380 hanya terbuka di jaringan privat.
Jalankan etcd di ketiga node, lalu verifikasi:
Terminal
sudo systemctl enable --now etcd
Command
etcdctl --endpoints=http://10.10.0.11:2379 endpoint health
Output yang diharapkan
http://10.10.0.11:2379 is healthy: successfully committed proposal
Jika etcdctl mengeluhkan versi API, tambahkan variabel lingkungan ETCDCTL_API=3.
Instalasi Patroni
Jalankan pada ketiga node.
Terminal
sudo apt install -y patroni python3-psycopg2
sudo systemctl stop postgresql@16-main
sudo systemctl disable postgresql@16-main
Layanan PostgreSQL bawaan sistem harus dinonaktifkan. Mulai titik ini, Patroni yang bertanggung jawab menjalankan dan menghentikan PostgreSQL. Dua pengelola proses yang sama-sama mengatur satu instance akan saling bertabrakan.
Bersihkan direktori data pada ketiga node agar Patroni melakukan bootstrap dari awal:
Terminal
sudo -u postgres rm -rf /var/lib/postgresql/16/main
sudo -u postgres mkdir -p /var/lib/postgresql/16/main
sudo chmod 700 /var/lib/postgresql/16/main
Nama paket dan versi Patroni pada Ubuntu 24.04 sebaiknya diverifikasi dengan apt policy patroni. Konfigurasi di bawah mengikuti format Patroni 3.x. []
Konfigurasi Patroni
/etc/patroni/patroni.yml
scope: pg-ha-cluster
namespace: /service/
name: pg-node-1
restapi:
listen: 10.10.0.11:8008
connect_address: 10.10.0.11:8008
etcd3:
hosts:
- 10.10.0.11:2379
- 10.10.0.12:2379
- 10.10.0.13:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
synchronous_mode: false
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: "on"
wal_log_hints: "on"
max_wal_senders: 10
max_replication_slots: 10
max_connections: 200
initdb:
- encoding: UTF8
- data-checksums
pg_hba:
- host replication replicator 10.10.0.0/24 scram-sha-256
- host all all 10.10.0.0/24 scram-sha-256
- local all all peer
postgresql:
listen: 0.0.0.0:5432
connect_address: 10.10.0.11:5432
data_dir: /var/lib/postgresql/16/main
bin_dir: /usr/lib/postgresql/16/bin
config_dir: /var/lib/postgresql/16/main
pgpass: /var/lib/postgresql/.pgpass_patroni
authentication:
replication:
username: replicator
password: ganti_password_replikasi
superuser:
username: postgres
password: ganti_password_superuser
parameters:
unix_socket_directories: /var/run/postgresql
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
Salin berkas ini ke ketiga node, lalu ubah name, restapi.listen, restapi.connect_address, postgresql.listen, dan postgresql.connect_address sesuai node masing-masing. Nilai scope wajib sama di ketiga node karena itulah identitas cluster di etcd.
Bagian bootstrap hanya dieksekusi satu kali, yaitu saat cluster pertama kali dibuat. Perubahan pada bagian ini setelah cluster berjalan tidak berpengaruh, dan perubahan parameter selanjutnya dilakukan melalui patronictl edit-config.
Parameter ttl: 30 menentukan berapa lama kunci leader bertahan tanpa pembaruan. Nilai ini secara langsung menentukan waktu deteksi kegagalan. Menurunkannya mempercepat failover, tetapi meningkatkan risiko failover palsu akibat gangguan jaringan sesaat.
Parameter maximum_lag_on_failover membatasi seberapa tertinggal sebuah standby masih boleh dipromosikan. Nilai di atas setara dengan 1 MB WAL. Ini adalah pertukaran langsung antara ketersediaan dan potensi kehilangan data.
Parameter use_pg_rewind: true memungkinkan Patroni menyambungkan kembali bekas primary secara otomatis setelah failover, tanpa penyalinan ulang seluruh database.
Perhatikan bahwa config_dir diarahkan ke direktori data, bukan ke /etc/postgresql/16/main. Patroni mengelola postgresql.conf sendiri, sehingga tata letak konfigurasi bawaan Debian tidak lagi dipakai.
Siapkan permission berkas konfigurasi, karena berisi password:
Terminal
sudo chown -R postgres:postgres /etc/patroni
sudo chmod 600 /etc/patroni/patroni.yml
Unit Systemd untuk Patroni
Paket Patroni pada Debian dan Ubuntu umumnya sudah menyertakan unit systemd bawaan dengan konvensi penamaan berkas konfigurasi tersendiri. Unit eksplisit berikut dipakai agar lokasi konfigurasi tidak ambigu. Periksa lebih dulu dengan systemctl cat patroni apakah unit bawaan sudah ada. []
/etc/systemd/system/patroni.service
[Unit]
Description=Patroni PostgreSQL HA Cluster
After=network-online.target etcd.service
Wants=network-online.target
[Service]
Type=simple
User=postgres
Group=postgres
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=process
TimeoutSec=30
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
Nilai KillMode=process penting agar systemd tidak ikut menghentikan proses PostgreSQL secara paksa ketika Patroni dihentikan. Verifikasi lokasi biner dengan which patroni sebelum menjalankan unit ini.
Jalankan Patroni pada node pertama terlebih dahulu, tunggu sampai node tersebut menjadi leader, baru jalankan pada dua node lainnya:
Terminal
sudo systemctl daemon-reload
sudo systemctl enable --now patroni
sudo journalctl -u patroni -f
Node kedua dan ketiga akan melakukan pg_basebackup otomatis dari leader dan bergabung sebagai replica.
Verifikasi Cluster
Command
sudo -u postgres patronictl -c /etc/patroni/patroni.yml list
Output yang diharapkan
+ Cluster: pg-ha-cluster (7361...) -----------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+-----------+------------+---------+-----------+----+-----------+
| pg-node-1 | 10.10.0.11 | Leader | running | 1 | |
| pg-node-2 | 10.10.0.12 | Replica | streaming | 1 | 0 |
| pg-node-3 | 10.10.0.13 | Replica | streaming | 1 | 0 |
+-----------+------------+---------+-----------+----+-----------+
Kolom TL menunjukkan timeline. Angka yang sama di seluruh node menandakan tidak ada divergensi. Setiap failover menaikkan nilai timeline.
Uji switchover terencana:
Command
sudo -u postgres patronictl -c /etc/patroni/patroni.yml switchover
Perintah switchover dipakai untuk perpindahan terencana, misalnya saat maintenance, dan berlangsung tanpa kehilangan data karena Patroni memastikan standby sudah menyusul sebelum promosi. Perintah failover dipakai ketika leader sudah tidak sehat.
Uji failover tidak terencana dengan mematikan node leader secara paksa, lalu amati keluaran patronictl list dari node lain. Leader baru seharusnya muncul dalam rentang waktu yang dipengaruhi nilai ttl dan loop_wait.
HAProxy sebagai Endpoint Tunggal
Aplikasi tidak boleh menyimpan alamat IP node primary secara statis, karena alamat itu berubah setiap kali failover terjadi. HAProxy memanfaatkan REST API Patroni untuk selalu mengarahkan koneksi ke node yang tepat.
Terminal
sudo apt install -y haproxy
/etc/haproxy/haproxy.cfg
global
maxconn 1000
log /dev/log local0
defaults
log global
mode tcp
retries 2
timeout client 30m
timeout connect 4s
timeout server 30m
timeout check 5s
listen stats
mode http
bind *:7000
stats enable
stats uri /
listen postgres_write
bind *:5000
option httpchk GET /primary
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server pg-node-1 10.10.0.11:5432 maxconn 300 check port 8008
server pg-node-2 10.10.0.12:5432 maxconn 300 check port 8008
server pg-node-3 10.10.0.13:5432 maxconn 300 check port 8008
listen postgres_read
bind *:5001
balance roundrobin
option httpchk GET /replica
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server pg-node-1 10.10.0.11:5432 maxconn 300 check port 8008
server pg-node-2 10.10.0.12:5432 maxconn 300 check port 8008
server pg-node-3 10.10.0.13:5432 maxconn 300 check port 8008
Kunci konfigurasi ini ada pada health check yang menyasar port 8008, yaitu REST API Patroni, bukan port PostgreSQL. Endpoint /primary hanya mengembalikan status 200 pada node yang sedang menjadi leader, sedangkan /replica hanya pada node standby yang sehat. Dengan cara ini HAProxy tidak perlu menebak peran node.
Port 5000 dipakai untuk koneksi baca-tulis, sedangkan port 5001 menyebarkan beban baca ke seluruh standby. Aplikasi yang membutuhkan konsistensi baca-setelah-tulis harus tetap menggunakan port 5000, karena replikasi asynchronous membuat standby bisa tertinggal beberapa saat.
Opsi on-marked-down shutdown-sessions memutus koneksi lama saat sebuah node dinyatakan tidak sehat. Tanpa opsi ini, koneksi yang sudah terbentuk ke bekas primary bisa bertahan dan menghasilkan error yang membingungkan.
Terminal
sudo systemctl restart haproxy
sudo systemctl enable haproxy
Uji koneksi melalui HAProxy:
Command
psql -h 10.10.0.11 -p 5000 -U postgres -c "SELECT inet_server_addr(), pg_is_in_recovery();"
Output yang diharapkan
inet_server_addr | pg_is_in_recovery
------------------+-------------------
10.10.0.11 | f
(1 row)
Satu instance HAProxy tetap merupakan titik kegagalan tunggal. Untuk menghilangkannya, jalankan dua instance HAProxy dengan virtual IP yang dikelola Keepalived, atau letakkan HAProxy pada setiap server aplikasi.
Konfigurasi Firewall
Buka hanya port yang diperlukan di jaringan privat.
Terminal
sudo ufw allow from 10.10.0.0/24 to any port 5432 proto tcp
sudo ufw allow from 10.10.0.0/24 to any port 8008 proto tcp
sudo ufw allow from 10.10.0.0/24 to any port 2379 proto tcp
sudo ufw allow from 10.10.0.0/24 to any port 2380 proto tcp
sudo ufw enable
Port 5432 sebaiknya tidak pernah terbuka ke internet. Jika aplikasi berada di luar jaringan privat, gunakan tunnel atau VPN, bukan aturan firewall yang dilonggarkan.
Troubleshooting
Standby tidak terhubung dengan pesan otentikasi gagal
Periksa pg_hba.conf di primary dan pastikan baris replication mencakup alamat IP standby. Ingat bahwa perubahan pg_hba.conf membutuhkan SELECT pg_reload_conf(); atau systemctl reload, bukan sekadar restart aplikasi klien.
Pesan requested WAL segment has already been removed
Standby tertinggal terlalu jauh dan WAL yang dibutuhkan sudah didaur ulang. Gunakan replication slot untuk mencegahnya. Jika sudah terlanjur terjadi, standby harus dibangun ulang dengan pg_basebackup.
Disk primary penuh secara perlahan tanpa pertumbuhan data
Periksa apakah ada replication slot yang tidak aktif dengan SELECT slot_name, active, restart_lsn FROM pg_replication_slots;. Slot dengan active = f menahan WAL tanpa ada yang membacanya. Hapus dengan pg_drop_replication_slot().
Patroni gagal start dengan pesan tidak dapat terhubung ke etcd
Verifikasi kesehatan etcd dengan etcdctl endpoint health. Kesalahan yang paling sering terjadi adalah ETCD_INITIAL_CLUSTER yang tidak identik di seluruh node, atau ETCD_NAME yang tidak cocok dengan nama pada daftar tersebut.
Patroni menjalankan PostgreSQL tetapi node tetap berstatus start failed
Baca log PostgreSQL di /var/lib/postgresql/16/main/log atau /var/log/postgresql. Penyebab umum adalah permission direktori data yang bukan 0700, bin_dir yang salah, atau sisa proses PostgreSQL lama yang masih memegang port 5432.
HAProxy menandai semua node sebagai DOWN
Health check menyasar port 8008, bukan 5432. Pastikan REST API Patroni dapat dijangkau dari server HAProxy dengan curl http://10.10.0.11:8008/primary, dan pastikan firewall mengizinkan port tersebut.
Cluster berjalan tetapi tidak pernah melakukan failover otomatis
Periksa tag nofailover pada node kandidat, dan periksa apakah lag melebihi maximum_lag_on_failover. Patroni memang menolak mempromosikan standby yang tertinggal terlalu jauh, dan ini merupakan perilaku yang diinginkan.
Kesimpulan
Streaming replication menyelesaikan masalah redundansi data, sedangkan Patroni dan etcd menyelesaikan masalah pengambilan keputusan saat terjadi kegagalan. Keduanya adalah lapisan yang berbeda, dan lapisan kedua tidak ada gunanya tanpa lapisan pertama yang sehat.
Satu hal yang perlu ditegaskan, replikasi bukan pengganti backup. Perintah DROP TABLE yang keliru akan tereplikasi ke seluruh standby dalam hitungan milidetik. Cluster HA tetap membutuhkan backup dengan kemampuan point-in-time recovery.
Langkah lanjutan yang layak dikerjakan adalah menambahkan pgBackRest untuk backup dan PITR, mengaktifkan TLS pada etcd maupun koneksi PostgreSQL, memasang pemantauan lag replikasi dengan alert, serta menjadwalkan latihan failover secara berkala. Cluster HA yang belum pernah diuji failover-nya sebaiknya diperlakukan sebagai cluster yang belum tentu bisa melakukan failover.




