Pasangan primary-standby dari chapter sebelumnya terasa instan: INSERT di satu sisi terbaca di sisi lain dalam hitungan milidetik. Di jaringan produksi, “milidetik” itu tidak dijamin — standby bisa tertinggal beberapa detik, beberapa menit, bahkan beberapa jam. Selisih itulah replication lag: jarak antara keadaan primary dan keadaan standby pada satu momen. Lag bukan angka kosmetik; setiap baris yang belum tiba adalah data yang hilang dari mata pembaca standby, dan bencana yang menelan primary akan membawa serta semua data yang masih tertahan di celah lag. Chapter ini mengukur lag dengan dua ukuran berbeda — byte WAL dan waktu — lalu membedah penyebab dan ambang batasnya.
Lag dalam Byte: Jarak Dua Posisi WAL #
Ukuran paling fundamental untuk lag adalah byte: berapa WAL yang sudah ditulis primary tetapi belum di-replay standby. PostgreSQL menyimpan posisi WAL sebagai LSN (Log Sequence Number) — angka heksadesimal yang naik setiap kali WAL bertambah. Dari sisi primary, posisi terbaru tersedia lewat pg_current_wal_lsn(), sedangkan posisi terakhir yang dilaporkan standby tersimpan di kolom replay_lsn pada view pg_stat_replication. Fungsi pg_wal_lsn_diff() menghitung selisih keduanya dalam byte:
SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_byte
FROM pg_stat_replication;lag_byte = 0 berarti standby menempel sempurna pada primary. Angka positif berarti ada WAL sepanjang itu yang belum diterapkan — baris di dalamnya belum terlihat oleh pembaca standby. Karena berbasis selisih dua posisi (bukan isi data), ukuran ini stabil dan bisa dibandingkan lintas waktu: 10 MB lag selalu bermakna sama, entah tabelnya besar atau kecil.
Menyiapkan Pasangan Demo #
Seperti chapter-chapter sebelumnya, kita membangun pasangan server disposable sendiri di /home/z/my-project/tmp/rp_lag dan tidak menyentuh server utama course:
| Peran | Port | Keterangan khusus |
|---|---|---|
| Primary | 54355 | autovacuum=off agar WAL demo murni dari perintah kita |
| Standby | 54356 | wal_receiver_status_interval=1s agar laporan posisi replay cepat sampai ke primary |
Skrip berikut menyiapkan pasangan ini dan membiarkannya menyala untuk skrip-skrip sesudahnya:
#!/usr/bin/env bash
# Menyiapkan pasangan primary-standby untuk demo replication lag.
# Primary berjalan di port 54355 (autovacuum dimatikan agar WAL demo
# murni berasal dari perintah kita), standby di port 54356 dengan
# wal_receiver_status_interval=1s agar laporan posisi replay cepat sampai
# ke primary. Skrip berikutnya (ukur-byte-lag.sh) melanjutkan pasangan ini.
set -eu
PGBIN=/home/z/my-project/tools/dl/pgdeb/usr/lib/postgresql/17/bin
BASE=/home/z/my-project/tmp/rp_lag
PRI=$BASE/pgdata_pusat # primary, port 54355
STD=$BASE/pgdata_cabang # standby, port 54356
SOCK=$BASE/sock
# Buang sisa percobaan sebelumnya bila ada.
"$PGBIN/pg_ctl" -D "$PRI" -m immediate stop >/dev/null 2>&1 || true
"$PGBIN/pg_ctl" -D "$STD" -m immediate stop >/dev/null 2>&1 || true
rm -rf "$BASE"
mkdir -p "$SOCK"
# Polling: tunggu sampai sebuah query mengembalikan nilai yang diharapkan.
tunggu() { # port database sql nilai_yang_diharapkan
local port="$1" db="$2" sql="$3" harapan="$4" i hasil
for i in $(seq 1 150); do
hasil=$(psql -X -q -t -A -h 127.0.0.1 -p "$port" -U postgres -d "$db" \
-c "$sql" 2>/dev/null) || true
if [ "$hasil" = "$harapan" ]; then return 0; fi
sleep 0.2
done
echo "TIMEOUT: menunggu '$sql' menjadi $harapan (port $port)" >&2
return 1
}
echo "== Langkah 1: initdb dan jalankan primary di port 54355 =="
"$PGBIN/initdb" -D "$PRI" --no-locale -E UTF8 -U postgres >/dev/null 2>&1
"$PGBIN/pg_ctl" -D "$PRI" \
-o "-p 54355 -k $SOCK -c listen_addresses=127.0.0.1 -c autovacuum=off" \
-l "$BASE/primary.log" start >/dev/null 2>&1
tunggu 54355 postgres "SELECT 1;" "1"
echo "primary siap"
echo "== Langkah 2: data awal di primary =="
psql -X -h 127.0.0.1 -p 54355 -U postgres -d postgres \
-c "CREATE DATABASE toko;"
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "CREATE TABLE penjualan (id int PRIMARY KEY, pelanggan text, total numeric);" \
-c "INSERT INTO penjualan VALUES (1, 'Budi', 25000), (2, 'Siti', 18000);"
echo "== Langkah 3: pg_basebackup -R, lalu jalankan standby di port 54356 =="
PGDATABASE=postgres pg_basebackup -h 127.0.0.1 -p 54355 -U postgres \
-D "$STD" -R -X stream -c fast >/dev/null 2>&1
"$PGBIN/pg_ctl" -D "$STD" \
-o "-p 54356 -k $SOCK -c listen_addresses=127.0.0.1 -c cluster_name=standby_cabang -c wal_receiver_status_interval=1s" \
-l "$BASE/standby.log" start >/dev/null 2>&1
tunggu 54356 toko "SELECT count(*) FROM penjualan;" "2"
echo "standby menyala dan konsisten"
echo "== Langkah 4: koneksi streaming dari sisi primary =="
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT application_name, state, sync_state FROM pg_stat_replication;"Output yang Dihasilkan:
== Langkah 1: initdb dan jalankan primary di port 54355 ==
primary siap
== Langkah 2: data awal di primary ==
CREATE DATABASE
CREATE TABLE
INSERT 0 2
== Langkah 3: pg_basebackup -R, lalu jalankan standby di port 54356 ==
standby menyala dan konsisten
== Langkah 4: koneksi streaming dari sisi primary ==
application_name | state | sync_state
------------------+-----------+------------
standby_cabang | streaming | async
(1 row)Mengukur Lag dalam Byte, dari Nol ke Nol #
Lag di pasangan lokal yang sehat praktis nol, sehingga tidak menarik untuk diamati. Skrip berikut membuat lag yang sengaja dan stabil lewat recovery_min_apply_delay — parameter asli PostgreSQL untuk standby tertunda (dipakai di produksi sebagai jaring pengaman kesalahan manusia). Standby menerima WAL seperti biasa, tetapi menunda replay-nya 4 detik; selama jeda itulah kita mengukur:
#!/usr/bin/env bash
# Mengukur replication lag dalam byte WAL dengan pg_wal_lsn_diff().
# Skrip ini melanjutkan pasangan server dari siapkan-pasangan.sh:
# primary di port 54355, standby di port 54356, database toko.
# Standby di-restart dengan recovery_min_apply_delay=4s untuk
# menghasilkan lag yang stabil dan dapat diukur ulang.
set -eu
PGBIN=/home/z/my-project/tools/dl/pgdeb/usr/lib/postgresql/17/bin
BASE=/home/z/my-project/tmp/rp_lag
PRI=$BASE/pgdata_pusat
STD=$BASE/pgdata_cabang
SOCK=$BASE/sock
# Polling: tunggu sampai sebuah query mengembalikan nilai yang diharapkan.
tunggu() { # port database sql nilai_yang_diharapkan
local port="$1" db="$2" sql="$3" harapan="$4" i hasil
for i in $(seq 1 150); do
hasil=$(psql -X -q -t -A -h 127.0.0.1 -p "$port" -U postgres -d "$db" \
-c "$sql" 2>/dev/null) || true
if [ "$hasil" = "$harapan" ]; then return 0; fi
sleep 0.2
done
echo "TIMEOUT: menunggu '$sql' menjadi $harapan (port $port)" >&2
return 1
}
echo "== Langkah 1: pasangan sehat — lag byte kembali ke 0 =="
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "INSERT INTO penjualan VALUES (3, 'Ani', 32000), (4, 'Rudi', 15000);"
tunggu 54356 toko "SELECT count(*) FROM penjualan;" "4"
tunggu 54355 toko \
"SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) FROM pg_stat_replication WHERE application_name = 'standby_cabang';" \
"0"
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_byte FROM pg_stat_replication;"
echo "== Langkah 2: restart standby dengan jeda replay 4 detik =="
"$PGBIN/pg_ctl" -D "$STD" -m fast stop >/dev/null 2>&1
"$PGBIN/pg_ctl" -D "$STD" \
-o "-p 54356 -k $SOCK -c listen_addresses=127.0.0.1 -c cluster_name=standby_cabang -c wal_receiver_status_interval=1s -c recovery_min_apply_delay=4s" \
-l "$BASE/standby.log" start >/dev/null 2>&1
tunggu 54355 toko \
"SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) FROM pg_stat_replication WHERE application_name = 'standby_cabang';" \
"0"
echo "standby kembali tersambung, kini dengan jeda replay"
echo "== Langkah 3: 30 transaksi kecil di primary — ukur lag byte =="
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko <<'SQL'
INSERT INTO penjualan VALUES (11, 'Budi', 12000);
INSERT INTO penjualan VALUES (12, 'Siti', 9000);
INSERT INTO penjualan VALUES (13, 'Ani', 22000);
INSERT INTO penjualan VALUES (14, 'Rudi', 5000);
INSERT INTO penjualan VALUES (15, 'Budi', 15000);
INSERT INTO penjualan VALUES (16, 'Siti', 31000);
INSERT INTO penjualan VALUES (17, 'Ani', 8000);
INSERT INTO penjualan VALUES (18, 'Rudi', 27000);
INSERT INTO penjualan VALUES (19, 'Budi', 19000);
INSERT INTO penjualan VALUES (20, 'Siti', 6000);
INSERT INTO penjualan VALUES (21, 'Ani', 24000);
INSERT INTO penjualan VALUES (22, 'Rudi', 11000);
INSERT INTO penjualan VALUES (23, 'Budi', 30000);
INSERT INTO penjualan VALUES (24, 'Siti', 17000);
INSERT INTO penjualan VALUES (25, 'Ani', 9000);
INSERT INTO penjualan VALUES (26, 'Rudi', 21000);
INSERT INTO penjualan VALUES (27, 'Budi', 14000);
INSERT INTO penjualan VALUES (28, 'Siti', 26000);
INSERT INTO penjualan VALUES (29, 'Ani', 7000);
INSERT INTO penjualan VALUES (30, 'Rudi', 33000);
INSERT INTO penjualan VALUES (31, 'Budi', 16000);
INSERT INTO penjualan VALUES (32, 'Siti', 28000);
INSERT INTO penjualan VALUES (33, 'Ani', 13000);
INSERT INTO penjualan VALUES (34, 'Rudi', 20000);
INSERT INTO penjualan VALUES (35, 'Budi', 10000);
INSERT INTO penjualan VALUES (36, 'Siti', 25000);
INSERT INTO penjualan VALUES (37, 'Ani', 18000);
INSERT INTO penjualan VALUES (38, 'Rudi', 15000);
INSERT INTO penjualan VALUES (39, 'Budi', 23000);
INSERT INTO penjualan VALUES (40, 'Siti', 12000);
SQL
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_byte FROM pg_stat_replication;"
echo "jumlah baris di standby (replay tertunda):"
psql -X -h 127.0.0.1 -p 54356 -U postgres -d toko \
-c "SELECT count(*) AS baris_standby FROM penjualan;"
echo "== Langkah 4: tunggu jeda terlewati — lag menyembuhkan =="
tunggu 54355 toko \
"SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) FROM pg_stat_replication WHERE application_name = 'standby_cabang';" \
"0"
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_byte FROM pg_stat_replication;"
psql -X -h 127.0.0.1 -p 54356 -U postgres -d toko \
-c "SELECT count(*) AS baris_standby FROM penjualan;"Output yang Dihasilkan:
== Langkah 1: pasangan sehat — lag byte kembali ke 0 ==
INSERT 0 2
lag_byte
----------
0
(1 row)
== Langkah 2: restart standby dengan jeda replay 4 detik ==
standby kembali tersambung, kini dengan jeda replay
== Langkah 3: 30 transaksi kecil di primary — ukur lag byte ==
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
lag_byte
----------
5144
(1 row)
jumlah baris di standby (replay tertunda):
baris_standby
---------------
4
(1 row)
== Langkah 4: tunggu jeda terlewati — lag menyembuhkan ==
lag_byte
----------
0
(1 row)
baris_standby
---------------
34
(1 row)Mari kita baca putaran lengkapnya:
- Langkah 1 — pasangan sehat: dua baris baru ditulis, standby mengejar, lalu
lag_bytekembali0. Polling di sini memakaipg_wal_lsn_diff(...)itu sendiri sebagai kondisi berhenti — kita menunggu sampai selisihnya persis nol, bukan sekadar “kira-kira sudah”. - Langkah 3 — lag muncul: 30 transaksi kecil di-commit berturut-turut di primary. Standby menerima WAL-nya, tetapi karena jeda replay 4 detik, belum satu pun commit diterapkan —
lag_bytemelonjak ke5144byte sementarabaris_standbymasih4. Itulah definisi lag dalam bentuk paling konkret: 5144 byte WAL berisi 30 transaksi yang belum dieksekusi standby. - Langkah 4 — lag menyembuhkan diri: begitu jeda 4 detik terlewati, standby me-replay seluruh tumpukan,
lag_byteturun kembali ke0, danbaris_standbymenjadi34. Pola “naik lalu turun ke nol” inilah kurva yang sehat di dashboard monitoring; yang berbahaya adalah kurva yang naik dan tidak pernah turun.
Lag dalam Waktu: Tiga Kolom Interval #
Byte menjawab “berapa banyak”; waktu menjawab “berapa lama”. View pg_stat_replication menyediakan tiga kolom interval yang membagi perjalanan WAL menjadi tiga tahap: write_lag (berapa lama sejak WAL di-flush lokal sampai standby menulis-nya), flush_lag (sampai standby men-flush-nya ke disk), dan replay_lag (sampai standby benar-benar mengeksekusinya). Ketiganya hanya terisi saat ada aktivitas; ketika pasangan diam, kolom-kolom itu menjadi NULL.
Nilai-nilai interval bergantung pada momen pengukuran dan kecepatan mesin, jadi blok output di bawah tidak diisi otomatis — angka di mesin Anda akan berbeda. Skrip berikut melanjutkan pasangan yang masih tertunda 4 detik dan mengukur di tiga momen berbeda:
#!/usr/bin/env bash
# Kolom lag berbasis waktu di pg_stat_replication: write_lag, flush_lag,
# replay_lag. Nilainya bergantung momen pengukuran dan pasti berbeda di
# mesin Anda, jadi blok outputnya ditandai manual di index.md — jalankan
# skrip ini sendiri untuk melihat angka Anda. Skrip ini melanjutkan
# pasangan server dari siapkan-pasangan.sh + ukur-byte-lag.sh
# (standby masih menunda replay 4 detik lewat recovery_min_apply_delay).
set -eu
echo "== Aktivitas segar: 5 transaksi kecil di primary =="
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko <<'SQL'
INSERT INTO penjualan VALUES (41, 'Budi', 11000);
INSERT INTO penjualan VALUES (42, 'Siti', 22000);
INSERT INTO penjualan VALUES (43, 'Ani', 33000);
INSERT INTO penjualan VALUES (44, 'Rudi', 44000);
INSERT INTO penjualan VALUES (45, 'Budi', 55000);
SQL
echo "== Segera setelah aktivitas: WAL sudah tersambung ke standby =="
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT application_name, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"
echo "baris di standby (5 transaksi di atas belum direplay):"
psql -X -h 127.0.0.1 -p 54356 -U postgres -d toko \
-c "SELECT count(*) AS baris_standby FROM penjualan;"
echo "== Setelah 4,5 detik: replay_lag memuncak karena replay tertunda =="
sleep 4.5
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT application_name, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"
echo "baris di standby kini:"
psql -X -h 127.0.0.1 -p 54356 -U postgres -d toko \
-c "SELECT count(*) AS baris_standby FROM penjualan;"
echo "== Setelah 2 detik tanpa aktivitas: kolom lag kembali NULL =="
sleep 2
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT application_name, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"Output yang Dihasilkan:
== Aktivitas segar: 5 transaksi kecil di primary ==
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
== Segera setelah aktivitas: WAL sudah tersambung ke standby ==
application_name | write_lag | flush_lag | replay_lag
------------------+-----------------+-----------------+----------------
standby_cabang | 00:00:00.000028 | 00:00:00.000028 | 00:00:00.00044
(1 row)
baris di standby (5 transaksi di atas belum direplay):
baris_standby
---------------
34
(1 row)
== Setelah 4,5 detik: replay_lag memuncak karena replay tertunda ==
application_name | write_lag | flush_lag | replay_lag
------------------+-----------------+-----------------+-----------------
standby_cabang | 00:00:00.000028 | 00:00:00.000028 | 00:00:04.003835
(1 row)
baris di standby kini:
baris_standby
---------------
39
(1 row)
== Setelah 2 detik tanpa aktivitas: kolom lag kembali NULL ==
application_name | write_lag | flush_lag | replay_lag
------------------+-----------+-----------+------------
standby_cabang | | |
(1 row)Output manual di atas diambil dari satu eksekusi nyata di mesin course — angka intervalnya (28 mikrodetik, 4,003835 detik) pasti berbeda di mesin Anda, tetapi polanya yang penting: write_lag dan flush_lag tetap mikroskopis di ketiga momen (WAL menyeberang jaringan lokal hampir instan), sementara replay_lag menembus 4 detik persis saat replay yang tertunda sedang dilaporkan. Kolom replay_lag adalah yang paling relevan untuk pembaca standby: selama nilainya 4 detik, ada 4 detik data yang belum bisa dibaca.
Penyebab Lag yang Umum #
Lag bukan satu penyakit melainkan gejala dari beberapa penyakit berbeda. Tabel berikut memetakan penyebab yang paling sering terjadi di produksi:
| Penyebab | Mekanisme | Pertanda khas |
|---|---|---|
| Jaringan lambat/sesak | WAL dikirim lebih lambat daripada tercipta | write_lag ikut membesar |
| Disk standby lambat | Flush di standby menyeret penerimaan | flush_lag dominan |
| Ledakan tulisan di primary | COPY massal, batch ETL — replay kalah cepatan | lag_byte naik tajam lalu turun |
| Query panjang di standby | Replay menunggu buffer yang dipakai query (recovery conflict) | Lag naik hanya saat query berat berjalan |
| Standby mati/berhenti | Tidak ada yang mengonsumsi WAL | Baris pg_stat_replication hilang; WAL menumpuk di slot |
Dua baris terakhir menutup lingkaran dengan chapter sebelumnya: baris pg_stat_replication yang lenyap saat standby mati, dan slot yang menahan WAL — keduanya sudah Anda buktikan sendiri di chapter Replication Slot
. Query panjang di standby bersinggungan dengan parameter hot_standby_feedback dan max_standby_streaming_delay, yang kita bahas tuntas di chapter Read Replica
.
Ambang Batas Alert dan Praktik Monitoring #
Angka lag yang “boleh” bergantung pada janji layanan Anda, tetapi beberapa prinsip berlaku umum. Pertama, pantau tren, bukan lonjakan sesaat: lag 200 MB selama 20 detik setelah batch ETL malam hari adalah normal; lag 5 MB yang bertahan 2 jam adalah kebocoran pelan. Kedua, gunakan dua ukuran sekaligus — byte untuk volume (bandingkan dengan laju WAL harian Anda) dan replay_lag untuk dampak pembaca. Ketiga, tetapkan dua tingkat: warning yang memberi tahu on-call (misalnya lag melewati 60 detik selama 5 menit) dan critical yang memicu tindakan (misalnya lag terus naik selama 15 menit, atau baris pg_stat_replication hilang).
Query pengukuran di chapter ini persis bentuk yang dipasang ke sistem monitoring: pg_wal_lsn_diff() menghasilkan satu angka yang mudah di-scrape exporter, dan pg_stat_replication sudah tercakup oleh PostgreSQL exporter standar. Cara menyusunnya menjadi dashboard dan alert — metrik, ambang, dan filosofi sinyal versus kebisingan — dibahas di chapter Monitoring dan Alerting
.
Mematikan Demo #
Skrip penutup menghentikan kedua server demo (standby lebih dulu, lalu primary) dan menghapus direktori datanya — server utama course di socket 54329 tidak pernah tersentuh sepanjang chapter:
#!/usr/bin/env bash
# Menghentikan kedua server demo replication lag dan menghapus
# direktori datanya. Server utama course (socket 54329) tidak disentuh.
set -eu
PGBIN=/home/z/my-project/tools/dl/pgdeb/usr/lib/postgresql/17/bin
BASE=/home/z/my-project/tmp/rp_lag
"$PGBIN/pg_ctl" -D "$BASE/pgdata_cabang" -m fast stop >/dev/null 2>&1 || true
echo "standby berhenti"
"$PGBIN/pg_ctl" -D "$BASE/pgdata_pusat" -m fast stop >/dev/null 2>&1 || true
echo "primary berhenti"
rm -rf "$BASE"
echo "direktori data demo dihapus"Output yang Dihasilkan:
standby berhenti
primary berhenti
direktori data demo dihapusLag yang terukur dan terpantau barulah setengah pekerjaan. Ada satu pertanyaan yang lebih mendasar bagi aplikasi: bagaimana sebenarnya rasanya membaca dari server yang tertinggal — dan apa yang bisa Anda lakukan ketika “tertinggal” itu menyakitkan pengguna? Itu topik chapter Read Replica .
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
bereskan-demo.sh
Unduh berkas#!/usr/bin/env bash
# Menghentikan kedua server demo replication lag dan menghapus
# direktori datanya. Server utama course (socket 54329) tidak disentuh.
set -eu
PGBIN=/home/z/my-project/tools/dl/pgdeb/usr/lib/postgresql/17/bin
BASE=/home/z/my-project/tmp/rp_lag
"$PGBIN/pg_ctl" -D "$BASE/pgdata_cabang" -m fast stop >/dev/null 2>&1 || true
echo "standby berhenti"
"$PGBIN/pg_ctl" -D "$BASE/pgdata_pusat" -m fast stop >/dev/null 2>&1 || true
echo "primary berhenti"
rm -rf "$BASE"
echo "direktori data demo dihapus"
lag-waktu.sh
Unduh berkas#!/usr/bin/env bash
# Kolom lag berbasis waktu di pg_stat_replication: write_lag, flush_lag,
# replay_lag. Nilainya bergantung momen pengukuran dan pasti berbeda di
# mesin Anda, jadi blok outputnya ditandai manual di index.md — jalankan
# skrip ini sendiri untuk melihat angka Anda. Skrip ini melanjutkan
# pasangan server dari siapkan-pasangan.sh + ukur-byte-lag.sh
# (standby masih menunda replay 4 detik lewat recovery_min_apply_delay).
set -eu
echo "== Aktivitas segar: 5 transaksi kecil di primary =="
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko <<'SQL'
INSERT INTO penjualan VALUES (41, 'Budi', 11000);
INSERT INTO penjualan VALUES (42, 'Siti', 22000);
INSERT INTO penjualan VALUES (43, 'Ani', 33000);
INSERT INTO penjualan VALUES (44, 'Rudi', 44000);
INSERT INTO penjualan VALUES (45, 'Budi', 55000);
SQL
echo "== Segera setelah aktivitas: WAL sudah tersambung ke standby =="
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT application_name, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"
echo "baris di standby (5 transaksi di atas belum direplay):"
psql -X -h 127.0.0.1 -p 54356 -U postgres -d toko \
-c "SELECT count(*) AS baris_standby FROM penjualan;"
echo "== Setelah 4,5 detik: replay_lag memuncak karena replay tertunda =="
sleep 4.5
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT application_name, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"
echo "baris di standby kini:"
psql -X -h 127.0.0.1 -p 54356 -U postgres -d toko \
-c "SELECT count(*) AS baris_standby FROM penjualan;"
echo "== Setelah 2 detik tanpa aktivitas: kolom lag kembali NULL =="
sleep 2
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT application_name, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"
siapkan-pasangan.sh
Unduh berkas#!/usr/bin/env bash
# Menyiapkan pasangan primary-standby untuk demo replication lag.
# Primary berjalan di port 54355 (autovacuum dimatikan agar WAL demo
# murni berasal dari perintah kita), standby di port 54356 dengan
# wal_receiver_status_interval=1s agar laporan posisi replay cepat sampai
# ke primary. Skrip berikutnya (ukur-byte-lag.sh) melanjutkan pasangan ini.
set -eu
PGBIN=/home/z/my-project/tools/dl/pgdeb/usr/lib/postgresql/17/bin
BASE=/home/z/my-project/tmp/rp_lag
PRI=$BASE/pgdata_pusat # primary, port 54355
STD=$BASE/pgdata_cabang # standby, port 54356
SOCK=$BASE/sock
# Buang sisa percobaan sebelumnya bila ada.
"$PGBIN/pg_ctl" -D "$PRI" -m immediate stop >/dev/null 2>&1 || true
"$PGBIN/pg_ctl" -D "$STD" -m immediate stop >/dev/null 2>&1 || true
rm -rf "$BASE"
mkdir -p "$SOCK"
# Polling: tunggu sampai sebuah query mengembalikan nilai yang diharapkan.
tunggu() { # port database sql nilai_yang_diharapkan
local port="$1" db="$2" sql="$3" harapan="$4" i hasil
for i in $(seq 1 150); do
hasil=$(psql -X -q -t -A -h 127.0.0.1 -p "$port" -U postgres -d "$db" \
-c "$sql" 2>/dev/null) || true
if [ "$hasil" = "$harapan" ]; then return 0; fi
sleep 0.2
done
echo "TIMEOUT: menunggu '$sql' menjadi $harapan (port $port)" >&2
return 1
}
echo "== Langkah 1: initdb dan jalankan primary di port 54355 =="
"$PGBIN/initdb" -D "$PRI" --no-locale -E UTF8 -U postgres >/dev/null 2>&1
"$PGBIN/pg_ctl" -D "$PRI" \
-o "-p 54355 -k $SOCK -c listen_addresses=127.0.0.1 -c autovacuum=off" \
-l "$BASE/primary.log" start >/dev/null 2>&1
tunggu 54355 postgres "SELECT 1;" "1"
echo "primary siap"
echo "== Langkah 2: data awal di primary =="
psql -X -h 127.0.0.1 -p 54355 -U postgres -d postgres \
-c "CREATE DATABASE toko;"
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "CREATE TABLE penjualan (id int PRIMARY KEY, pelanggan text, total numeric);" \
-c "INSERT INTO penjualan VALUES (1, 'Budi', 25000), (2, 'Siti', 18000);"
echo "== Langkah 3: pg_basebackup -R, lalu jalankan standby di port 54356 =="
PGDATABASE=postgres pg_basebackup -h 127.0.0.1 -p 54355 -U postgres \
-D "$STD" -R -X stream -c fast >/dev/null 2>&1
"$PGBIN/pg_ctl" -D "$STD" \
-o "-p 54356 -k $SOCK -c listen_addresses=127.0.0.1 -c cluster_name=standby_cabang -c wal_receiver_status_interval=1s" \
-l "$BASE/standby.log" start >/dev/null 2>&1
tunggu 54356 toko "SELECT count(*) FROM penjualan;" "2"
echo "standby menyala dan konsisten"
echo "== Langkah 4: koneksi streaming dari sisi primary =="
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT application_name, state, sync_state FROM pg_stat_replication;"
ukur-byte-lag.sh
Unduh berkas#!/usr/bin/env bash
# Mengukur replication lag dalam byte WAL dengan pg_wal_lsn_diff().
# Skrip ini melanjutkan pasangan server dari siapkan-pasangan.sh:
# primary di port 54355, standby di port 54356, database toko.
# Standby di-restart dengan recovery_min_apply_delay=4s untuk
# menghasilkan lag yang stabil dan dapat diukur ulang.
set -eu
PGBIN=/home/z/my-project/tools/dl/pgdeb/usr/lib/postgresql/17/bin
BASE=/home/z/my-project/tmp/rp_lag
PRI=$BASE/pgdata_pusat
STD=$BASE/pgdata_cabang
SOCK=$BASE/sock
# Polling: tunggu sampai sebuah query mengembalikan nilai yang diharapkan.
tunggu() { # port database sql nilai_yang_diharapkan
local port="$1" db="$2" sql="$3" harapan="$4" i hasil
for i in $(seq 1 150); do
hasil=$(psql -X -q -t -A -h 127.0.0.1 -p "$port" -U postgres -d "$db" \
-c "$sql" 2>/dev/null) || true
if [ "$hasil" = "$harapan" ]; then return 0; fi
sleep 0.2
done
echo "TIMEOUT: menunggu '$sql' menjadi $harapan (port $port)" >&2
return 1
}
echo "== Langkah 1: pasangan sehat — lag byte kembali ke 0 =="
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "INSERT INTO penjualan VALUES (3, 'Ani', 32000), (4, 'Rudi', 15000);"
tunggu 54356 toko "SELECT count(*) FROM penjualan;" "4"
tunggu 54355 toko \
"SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) FROM pg_stat_replication WHERE application_name = 'standby_cabang';" \
"0"
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_byte FROM pg_stat_replication;"
echo "== Langkah 2: restart standby dengan jeda replay 4 detik =="
"$PGBIN/pg_ctl" -D "$STD" -m fast stop >/dev/null 2>&1
"$PGBIN/pg_ctl" -D "$STD" \
-o "-p 54356 -k $SOCK -c listen_addresses=127.0.0.1 -c cluster_name=standby_cabang -c wal_receiver_status_interval=1s -c recovery_min_apply_delay=4s" \
-l "$BASE/standby.log" start >/dev/null 2>&1
tunggu 54355 toko \
"SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) FROM pg_stat_replication WHERE application_name = 'standby_cabang';" \
"0"
echo "standby kembali tersambung, kini dengan jeda replay"
echo "== Langkah 3: 30 transaksi kecil di primary — ukur lag byte =="
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko <<'SQL'
INSERT INTO penjualan VALUES (11, 'Budi', 12000);
INSERT INTO penjualan VALUES (12, 'Siti', 9000);
INSERT INTO penjualan VALUES (13, 'Ani', 22000);
INSERT INTO penjualan VALUES (14, 'Rudi', 5000);
INSERT INTO penjualan VALUES (15, 'Budi', 15000);
INSERT INTO penjualan VALUES (16, 'Siti', 31000);
INSERT INTO penjualan VALUES (17, 'Ani', 8000);
INSERT INTO penjualan VALUES (18, 'Rudi', 27000);
INSERT INTO penjualan VALUES (19, 'Budi', 19000);
INSERT INTO penjualan VALUES (20, 'Siti', 6000);
INSERT INTO penjualan VALUES (21, 'Ani', 24000);
INSERT INTO penjualan VALUES (22, 'Rudi', 11000);
INSERT INTO penjualan VALUES (23, 'Budi', 30000);
INSERT INTO penjualan VALUES (24, 'Siti', 17000);
INSERT INTO penjualan VALUES (25, 'Ani', 9000);
INSERT INTO penjualan VALUES (26, 'Rudi', 21000);
INSERT INTO penjualan VALUES (27, 'Budi', 14000);
INSERT INTO penjualan VALUES (28, 'Siti', 26000);
INSERT INTO penjualan VALUES (29, 'Ani', 7000);
INSERT INTO penjualan VALUES (30, 'Rudi', 33000);
INSERT INTO penjualan VALUES (31, 'Budi', 16000);
INSERT INTO penjualan VALUES (32, 'Siti', 28000);
INSERT INTO penjualan VALUES (33, 'Ani', 13000);
INSERT INTO penjualan VALUES (34, 'Rudi', 20000);
INSERT INTO penjualan VALUES (35, 'Budi', 10000);
INSERT INTO penjualan VALUES (36, 'Siti', 25000);
INSERT INTO penjualan VALUES (37, 'Ani', 18000);
INSERT INTO penjualan VALUES (38, 'Rudi', 15000);
INSERT INTO penjualan VALUES (39, 'Budi', 23000);
INSERT INTO penjualan VALUES (40, 'Siti', 12000);
SQL
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_byte FROM pg_stat_replication;"
echo "jumlah baris di standby (replay tertunda):"
psql -X -h 127.0.0.1 -p 54356 -U postgres -d toko \
-c "SELECT count(*) AS baris_standby FROM penjualan;"
echo "== Langkah 4: tunggu jeda terlewati — lag menyembuhkan =="
tunggu 54355 toko \
"SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) FROM pg_stat_replication WHERE application_name = 'standby_cabang';" \
"0"
psql -X -h 127.0.0.1 -p 54355 -U postgres -d toko \
-c "SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_byte FROM pg_stat_replication;"
psql -X -h 127.0.0.1 -p 54356 -U postgres -d toko \
-c "SELECT count(*) AS baris_standby FROM penjualan;"