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 sudo pada 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. [PERLU VERIFIKASI]

/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. [PERLU VERIFIKASI]

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. [PERLU VERIFIKASI]

/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.