PostgreSQL 10 hadir dengan tambahan selamat datang dari replikasi logis fitur. Ini memberikan cara yang lebih fleksibel dan lebih mudah untuk mereplikasi tabel Anda daripada mekanisme replikasi streaming biasa. Namun, itu memang memiliki beberapa batasan yang mungkin atau mungkin tidak mencegah Anda menggunakannya untuk replikasi. Baca terus untuk mempelajari lebih lanjut.
Apa Itu Replikasi Logis?
Replikasi Streaming
Sebelum v10, satu-satunya cara untuk mereplikasi data yang berada di server adalah dengan mereplikasi perubahan di tingkat WAL. Selama operasinya, server PostgreSQL(utama ) menghasilkan urutan file WAL. Ide dasarnya adalah memindahkan file-file ini ke server PostgreSQL lain (siaga ) yang mengambil file-file ini dan "memutar ulang" mereka untuk membuat ulang perubahan yang sama yang terjadi di server utama. Server siaga tetap dalam mode hanya baca yang disebutmode pemulihan , dan setiap perubahan pada server siaga tidak diizinkan (yaitu, hanya transaksi read-only yang diperbolehkan).
Proses pengiriman file WAL dari primer ke standby disebut logshipping , dan dapat dilakukan secara manual (skrip untuk rsync berubah dari$PGDATA/pg_wal
utama direktori ke sekunder) atau melalui replikasi streaming .Berbagai fitur seperti slot replikasi , umpan balik siaga dan kegagalan ditambahkan dari waktu ke waktu untuk meningkatkan keandalan dan kegunaan streamingreplikasi.
Satu "fitur" besar dari replikasi streaming adalah semuanya atau tidak sama sekali. Semua perubahan pada semua objek dari semua database di primer harus dikirimkan ke standby, dan standby harus mengimpor setiap perubahan. Tidak mungkin untuk secara selektif mereplikasi bagian dari database Anda.
Replikasi Logis
Replikasi Logis , ditambahkan di v10, memungkinkan untuk melakukan hal itu – hanya mereplikasi satu set tabel ke server lain. Hal ini paling baik dijelaskan dengan sebuah contoh. Mari kita ambil database bernama src
di server, dan buat tabel di dalamnya:
src=> CREATE TABLE t (col1 int, col2 int);
CREATE TABLE
src=> INSERT INTO t VALUES (1,10), (2,20), (3,30);
INSERT 0 3
Kami juga akan membuat publikasi dalam database ini (perhatikan bahwa Anda perlu memiliki hak pengguna super untuk melakukan ini):
src=# CREATE PUBLICATION mypub FOR ALL TABLES;
CREATE PUBLICATION
Sekarang mari kita ke database dst
di server lain dan buat tabel serupa:
dst=# CREATE TABLE t (col1 int, col2 int, col3 text NOT NULL DEFAULT 'foo');
CREATE TABLE
Dan sekarang kami menyiapkan langganan di sini yang akan terhubung ke publikasi di sumber dan mulai menarik perubahan. (Perhatikan bahwa Anda harus memiliki penggunarepuser
di server sumber dengan hak replikasi dan akses baca ke tabel.)
dst=# CREATE SUBSCRIPTION mysub CONNECTION 'user=repuser password=reppass host=127.0.0.1 port=5432 dbname=src' PUBLICATION mypub;
NOTICE: created replication slot "mysub" on publisher
CREATE SUBSCRIPTION
Perubahan disinkronkan, dan Anda dapat melihat baris di sisi tujuan:
dst=# SELECT * FROM t;
col1 | col2 | col3
------+------+------
1 | 10 | foo
2 | 20 | foo
3 | 30 | foo
(3 rows)
Tabel tujuan memiliki kolom tambahan “col3”, yang tidak tersentuh oleh replikasi. Perubahan direplikasi secara “logis” – jadi, selama mungkin untuk menyisipkan baris dengan t.col1 dan t.col2 saja, proses replikasi akan dilakukan.
Dibandingkan dengan replikasi streaming, fitur replikasi logis sempurna untuk mereplikasi, katakanlah, skema tunggal atau kumpulan tabel dalam database tertentu ke server lain.
Replikasi Perubahan Skema
Asumsikan Anda memiliki aplikasi Django dengan kumpulan tabelnya yang hidup dalam basis data sumber. Mudah dan efisien untuk menyiapkan replikasi logis untuk membawa semua tabel ini ke server lain, tempat Anda dapat menjalankan pelaporan, analitik, pekerjaan batch, aplikasi dukungan pengembang/pelanggan, dan sejenisnya tanpa menyentuh data "nyata" dan tanpa memengaruhi aplikasi produksi.
Mungkin batasan terbesar dari Replikasi Logis saat ini adalah ia tidak mereplikasi perubahan skema – perintah DDL apa pun yang dijalankan di basis data sumber tidak menyebabkan perubahan serupa di basis data tujuan, tidak seperti di replikasi streaming. Misalnya, jika kita melakukan ini di database sumber:
src=# ALTER TABLE t ADD newcol int;
ALTER TABLE
src=# INSERT INTO t VALUES (-1, -10, -100);
INSERT 0 1
ini akan dicatat di file log tujuan:
ERROR: logical replication target relation "public.t" is missing some replicated columns
dan replikasi berhenti. Kolom harus ditambahkan “secara manual” di tujuan, di mana replikasi dilanjutkan:
dst=# SELECT * FROM t;
col1 | col2 | col3
------+------+------
1 | 10 | foo
2 | 20 | foo
3 | 30 | foo
(3 rows)
dst=# ALTER TABLE t ADD newcol int;
ALTER TABLE
dst=# SELECT * FROM t;
col1 | col2 | col3 | newcol
------+------+------+--------
1 | 10 | foo |
2 | 20 | foo |
3 | 30 | foo |
-1 | -10 | foo | -100
(4 rows)
Ini berarti jika aplikasi Django Anda telah menambahkan fitur baru yang membutuhkan kolom atau tabel baru, dan Anda telah menjalankan django-admin migrate
pada sourcedatabase, pengaturan replikasi rusak.
Solusi
Cara terbaik Anda untuk memperbaiki masalah ini adalah dengan menjeda langganan di tujuan, memigrasikan tujuan terlebih dahulu, lalu sumber, lalu melanjutkan langganan. Anda dapat menjeda dan melanjutkan langganan seperti ini:
-- pause replication (destination side)
ALTER SUBSCRIPTION mysub DISABLE;
-- resume replication
ALTER SUBSCRIPTION mysub ENABLE;
Jika tabel baru ditambahkan dan publikasi Anda bukan “UNTUK SEMUA TABEL”, Anda harus menambahkannya ke publikasi secara manual:
ALTER PUBLICATION mypub ADD TABLE newly_added_table;
Anda juga harus "menyegarkan" langganan di sisi tujuan untuk memberi tahuPostgres agar mulai menyinkronkan tabel baru:
dst=# ALTER SUBSCRIPTION mysub REFRESH PUBLICATION;
ALTER SUBSCRIPTION
Urutan
Pertimbangkan tabel ini di sumbernya, memiliki urutan:
src=# CREATE TABLE s (a serial PRIMARY KEY, b text);
CREATE TABLE
src=# INSERT INTO s (b) VALUES ('foo'), ('bar'), ('baz');
INSERT 0 3
src=# SELECT * FROM s;
a | b
---+-----
1 | foo
2 | bar
3 | baz
(3 rows)
src=# SELECT currval('s_a_seq'), nextval('s_a_seq');
currval | nextval
---------+---------
3 | 4
(1 row)
Urutan s_a_seq
dibuat untuk mendukung a
kolom, dari serial
type.Ini menghasilkan nilai peningkatan otomatis untuk s.a
. Sekarang mari kita tiru ini menjadi dst
, dan masukkan baris lain:
dst=# SELECT * FROM s;
a | b
---+-----
1 | foo
2 | bar
3 | baz
(3 rows)
dst=# INSERT INTO s (b) VALUES ('foobaz');
ERROR: duplicate key value violates unique constraint "s_pkey"
DETAIL: Key (a)=(1) already exists.
dst=# SELECT currval('s_a_seq'), nextval('s_a_seq');
currval | nextval
---------+---------
1 | 2
(1 row)
Ups, apa yang baru saja terjadi? Tujuan mencoba memulai urutan dari awal dan menghasilkan nilai 1 untuk a
. Ini karena replikasi logis tidak mereplikasi nilai untuk urutan karena nilai berikutnya dari urutan ini tidak disimpan dalam tabel itu sendiri.
Solusi
Jika Anda memikirkannya secara logis, Anda tidak dapat mengubah nilai "peningkatan otomatis" yang sama dari dua tempat tanpa sinkronisasi dua arah. Jika Anda benar-benar membutuhkan jumlah yang bertambah di setiap baris tabel, dan perlu memasukkan ke dalam tabel itu dari beberapa server, Anda dapat:
- gunakan sumber eksternal untuk nomor tersebut, seperti ZooKeeper atau dll,
- gunakan rentang yang tidak tumpang tindih – misalnya, server pertama menghasilkan dan memasukkan angka dalam rentang 1 hingga 1 juta, yang kedua dalam rentang 1 juta hingga 2 juta, dan seterusnya.
Tabel Tanpa Baris Unik
Mari kita coba membuat tabel tanpa kunci utama, dan mereplikasinya:
src=# CREATE TABLE nopk (foo text);
CREATE TABLE
src=# INSERT INTO nopk VALUES ('new york');
INSERT 0 1
src=# INSERT INTO nopk VALUES ('boston');
INSERT 0 1
Dan baris sekarang juga ada di tujuan:
dst=# SELECT * FROM nopk;
foo
----------
new york
boston
(2 rows)
Sekarang mari kita coba menghapus baris kedua di sumbernya:
src=# DELETE FROM nopk WHERE foo='boston';
ERROR: cannot delete from table "nopk" because it does not have a replica identity and publishes deletes
HINT: To enable deleting from the table, set REPLICA IDENTITY using ALTER TABLE.
Ini terjadi karena tujuan tidak akan dapat mengidentifikasi secara unik baris yang perlu dihapus (atau diperbarui) tanpa kunci utama.
Solusi
Anda tentu saja dapat mengubah skema untuk menyertakan kunci utama. Jika Anda tidak ingin melakukannya, Anda ALTER TABLE
dan atur "identifikasi replika" ke baris penuh atau indeks unik. Misalnya:
src=# ALTER TABLE nopk REPLICA IDENTITY FULL;
ALTER TABLE
src=# DELETE FROM nopk WHERE foo='boston';
DELETE 1
Penghapusan sekarang berhasil, dan replikasi juga:
dst=# SELECT * FROM nopk;
foo
----------
new york
(1 row)
Jika tabel Anda benar-benar tidak memiliki cara untuk mengidentifikasi baris secara unik, maka Anda sedikit buntu. Lihat bagian REPLICA IDENTITY dari ALTERTABLE untuk informasi lebih lanjut.
Tujuan yang Dipartisi Secara Berbeda
Bukankah lebih baik memiliki sumber yang dipartisi dengan satu cara dan tujuan dengan cara yang berbeda? Misalnya, di sumber kita dapat menyimpan paritas untuk setiap bulan, dan di tempat tujuan untuk setiap tahun. Agaknya tujuannya adalah mesin yang lebih besar, dan kita perlu menyimpan data historis, tetapi jarang membutuhkan data itu.
Mari buat tabel dengan partisi bulanan di sumbernya:
src=# CREATE TABLE measurement (
src(# logdate date not null,
src(# peaktemp int
src(# ) PARTITION BY RANGE (logdate);
CREATE TABLE
src=#
src=# CREATE TABLE measurement_y2019m01 PARTITION OF measurement
src-# FOR VALUES FROM ('2019-01-01') TO ('2019-02-01');
CREATE TABLE
src=#
src=# CREATE TABLE measurement_y2019m02 PARTITION OF measurement
src-# FOR VALUES FROM ('2019-02-01') TO ('2019-03-01');
CREATE TABLE
src=#
src=# GRANT SELECT ON measurement, measurement_y2019m01, measurement_y2019m02 TO repuser;
GRANT
Dan coba buat tabel yang dipartisi tahunan di tujuan:
dst=# CREATE TABLE measurement (
dst(# logdate date not null,
dst(# peaktemp int
dst(# ) PARTITION BY RANGE (logdate);
CREATE TABLE
dst=#
dst=# CREATE TABLE measurement_y2018 PARTITION OF measurement
dst-# FOR VALUES FROM ('2018-01-01') TO ('2019-01-01');
CREATE TABLE
dst=#
dst=# CREATE TABLE measurement_y2019 PARTITION OF measurement
dst-# FOR VALUES FROM ('2019-01-01') TO ('2020-01-01');
CREATE TABLE
dst=#
dst=# ALTER SUBSCRIPTION mysub REFRESH PUBLICATION;
ERROR: relation "public.measurement_y2019m01" does not exist
dst=#
Postgres mengeluh bahwa ia memerlukan tabel partisi untuk Jan 2019, yang tidak ingin kami buat di tujuan.
Ini terjadi karena replikasi logis tidak berfungsi pada tingkat tabel dasar, tetapi pada tingkat tabel anak. Tidak ada solusi nyata untuk ini – jika Anda menggunakan partisi, hierarki partisi harus sama di kedua sisi penyiapan replikasi alogis.
Objek Besar
Objek besar tidak dapat direplikasi menggunakan replikasi logis. Ini mungkin bukan masalah besar saat ini, karena menyimpan benda besar bukanlah praktik modern yang umum. Juga lebih mudah untuk menyimpan referensi ke objek besar di beberapa penyimpanan eksternal yang berlebihan (seperti NFS, S3, dll.) dan mereplikasi referensi itu daripada menyimpan dan mereplikasi objek itu sendiri.