Sao chép luồng ở phần trước sao chép byte. Sao chép logic sao chép dòng. Khác biệt đó mở ra những thứ mà cách kia không làm được, và mang theo ba chỗ hỏng mà cách kia không có.
Dựng
Cần wal_level = logical ở cả hai bên. Rồi ba câu lệnh:
-- máy nguồn
create publication p_all for table d2, khong_khoa;
-- máy đích
create subscription s_all
connection 'host=m1 dbname=postgres user=postgres password=x'
publication p_all;
Đồng bộ ban đầu 500.000 dòng trong 1,2 giây, tự động, không cần pg_basebackup.
Chú ý câu create publication liệt kê từng bảng. Đó là khác biệt đầu tiên và là lý do chính người ta chọn cách này.
So sánh với sao chép luồng
| Sao chép luồng | Sao chép logic | |
|---|---|---|
| Độ trễ dưới tải | 1,4 – 25 kB | 55 – 303 kB |
| Độ trễ theo giây | dưới 0,003 s | 0,00 s |
| Chọn được từng bảng | không | có |
| Máy đích ghi được | không (chỉ đọc) | có |
| Khác phiên bản PostgreSQL | không | có |
| DDL, chuỗi, quyền | có (sao byte) | không |
Độ trễ của sao chép logic cao hơn khoảng mười lần tính theo byte, nhưng cả hai đều dưới một phần trăm giây — với phần lớn ứng dụng thì không phân biệt được.
Bốn dòng giữa là lý do sao chép logic tồn tại. Nâng cấp PostgreSQL 13 lên 16 mà không ngừng dịch vụ, gửi vài bảng sang hệ thống phân tích, gộp dữ liệu từ nhiều máy về một chỗ — sao chép luồng không làm được cái nào.
Dòng cuối là toàn bộ phần còn lại của bài này.
Chỗ hỏng 1: bảng không có khoá chính
update khong_khoa set v = 'da sua' where id = 1;
ERROR: cannot update table "khong_khoa" because it does not have a replica identity
and publishes updates
HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
Lỗi nổ ra trên máy nguồn và chặn hẳn câu lệnh.
Sao chép logic gửi đi "dòng nào đã đổi", và nó cần một cách để chỉ ra dòng đó ở máy đích. Mặc định nó dùng khoá chính. Không có khoá chính thì không có cách nào chỉ.
Điểm tốt: nó từ chối ngay thay vì để hai bên phân kỳ âm thầm. Điểm không tốt: bảng của bạn đột nhiên không UPDATE được nữa, và lỗi xuất hiện ở tầng ứng dụng.
Cách chữa:
alter table khong_khoa replica identity full;
Sau đó DELETE chạy được và hai bên đều còn 99 dòng.
REPLICA IDENTITY FULL nghĩa là gửi toàn bộ dòng cũ để máy đích tự tìm. Nó tốn WAL hơn nhiều và khiến máy đích phải quét tuần tự cho mỗi thay đổi — trên bảng lớn thì rất chậm. Đặt khoá chính vẫn là cách đúng.
Chỗ hỏng 2: DDL không được sao chép, và nó làm đứt hẳn
Đây là chỗ đáng nhớ nhất.
-- chỉ chạy trên máy nguồn
alter table d2 add column moi text default 'x';
Nguồn có 4 cột, đích còn 3. Rồi chèn một dòng dùng cột mới:
ERROR: logical replication target relation "public.d2" is missing replicated column: "moi"
LOG: background worker "logical replication worker" (PID 226) exited with exit code 1
Máy đích nhận 0 dòng mới. apply_error_count lên 7. Tiến trình áp dụng chết đi sống lại liên tục, và sao chép dừng hoàn toàn cho tới khi ai đó sửa lược đồ.
Chữa bằng cách thêm cột trên máy đích:
alter table d2 add column moi text;
Đo được: ngay sau đó máy đích nhận đủ dòng và độ trễ về 0 byte. Nó không mất dữ liệu — nó chỉ đợi.
Điều này định ra một quy tắc vận hành cứng: mọi thay đổi lược đồ phải áp lên máy đích trước, máy nguồn sau. Thêm cột thì đích trước; xoá cột thì nguồn trước. Làm ngược là sao chép đứng.
Có công cụ tự động hoá chuyện này, nhưng PostgreSQL bản thân nó không làm.
Chỗ hỏng 3: chuỗi không được sao chép
Đây là chỗ nguy hiểm nhất vì nó hoàn toàn im lặng.
Chèn 1.000 dòng vào bảng có cột serial:
nguồn 1.000 dòng chuỗi ở giá trị 1.000
đích 1.000 dòng chuỗi ở giá trị 1
Dữ liệu khớp hoàn toàn. Mọi kiểm tra bạn nghĩ ra đều xanh. Nhưng chuyển đổi sang máy đích rồi chèn dòng đầu tiên là trùng khoá chính ngay lập tức — và tiếp tục trùng 999 lần nữa.
Sao chép logic gửi giá trị id như một giá trị bình thường; nó không đụng tới trạng thái của chuỗi. Đó là hành vi hợp lý (chuỗi không phải dữ liệu bảng) và là cái bẫy hoàn hảo.
Bước bắt buộc trong mọi quy trình chuyển đổi:
-- trên máy đích, sau khi ngừng ghi ở nguồn
select setval('sq_id_seq', (select max(id) from sq));
Với nhiều bảng thì phải sinh câu lệnh cho từng chuỗi một. Đây là bước duy nhất trong bài mà quên nó nghĩa là hệ thống hỏng ngay khi bạn chuyển sang.
Ngoài chuỗi, những thứ khác cũng không được sao chép: quyền GRANT, CREATE INDEX, CREATE VIEW, và bảng mới thêm vào sau (phải alter publication ... add table rồi alter subscription ... refresh publication).
Khi nào dùng cách nào
| Tình huống | Nên |
|---|---|
| Máy dự phòng để chuyển đổi khi sự cố | Sao chép luồng |
| Bản sao chỉ đọc cho báo cáo | Sao chép luồng |
| Nâng cấp phiên bản lớn không ngừng dịch vụ | Sao chép logic |
| Gửi vài bảng sang hệ thống phân tích | Sao chép logic |
| Gộp dữ liệu từ nhiều máy về một chỗ | Sao chép logic |
| Máy đích cần ghi được | Sao chép logic |
Hai dòng đầu là lý do sao chép luồng vẫn là mặc định: nó sao chép mọi thứ, nên máy dự phòng thật sự là bản sao đầy đủ. Sao chép logic cho bạn một cơ sở dữ liệu giống về dữ liệu nhưng không giống về lược đồ, chuỗi, quyền, và chỉ mục — trừ khi bạn tự lo hết.
Theo dõi
Trên máy nguồn:
select slot_name, active, wal_status,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) as do_tre
from pg_replication_slots where slot_type = 'logical';
Trên máy đích:
select subname, apply_error_count, sync_error_count
from pg_stat_subscription_stats;
select subname, received_lsn, latest_end_lsn,
round(extract(epoch from now() - last_msg_receipt_time), 2) as giay_tu_tin_cuoi
from pg_stat_subscription;
apply_error_count khác 0 là dấu hiệu duy nhất cho biết sao chép đang đứng vì lược đồ lệch. Nó không tự sửa và không tự cảnh báo.
Và cảnh báo từ phần 51 vẫn đúng nguyên: khe sao chép logic cũng giữ WAL lại. Một subscription bị đứt vì thiếu cột sẽ khiến pg_wal trên máy nguồn phình lên cho tới khi hết đĩa — trừ khi bạn đã đặt max_slot_wal_keep_size.
Thử ba mươi giây
Nếu bạn đang chạy sao chép logic:
-- trên máy đích
select subname, apply_error_count from pg_stat_subscription_stats;
-- so lược đồ hai bên
select table_name, count(*) as so_cot
from information_schema.columns
where table_schema = 'public' group by table_name order by table_name;
Chạy câu thứ hai ở cả hai máy và so kết quả. Bất kỳ bảng nào lệch số cột là một quả bom hẹn giờ — nó chưa nổ chỉ vì chưa có ai ghi vào cột mới.
Và kiểm chuỗi:
select schemaname, sequencename, last_value from pg_sequences;
Nếu máy đích toàn số 1 trong khi nguồn có số lớn, bạn biết chuyện gì sẽ xảy ra vào ngày chuyển đổi.
Phần sau đo giám sát: những khung nhìn thống kê nào đáng theo dõi, và con số nào báo động sớm nhất.