PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

PostgreSQL Logical Replication Gotchas

PostgreSQL 10 ra mắt với sự bổ sung đáng hoan nghênh của bản sao lôgic tính năng. Điều này cung cấp một phương tiện linh hoạt hơn và dễ dàng hơn để sao chép các bảng của bạn so với cơ chế sao chép trực tuyến thông thường. Tuy nhiên, nó có một số hạn chế có thể ngăn cản bạn sử dụng nó để sao chép. Đọc để tìm hiểu thêm.

Vẫn sao chép hợp lý là gì?

Sao chép truyền trực tuyến

Trước v10, cách duy nhất để sao chép dữ liệu trong máy chủ là sao chép các thay đổi ở cấp WAL. Trong quá trình hoạt động, một máy chủ PostgreSQL ( chính ) tạo ra một chuỗi các tệp WAL. Ý tưởng cơ bản là chuyển các tệp này sang một máy chủ PostgreSQL khác ( chế độ chờ ) lấy các tệp bên trong và "phát lại" chúng để tạo lại các thay đổi tương tự xảy ra tại máy chủ chính. Máy chủ dự phòng vẫn ở chế độ chỉ đọc được gọi là chế độ khôi phục và bất kỳ thay đổi nào đối với máy chủ dự phòng đều không được phép (thatis, chỉ cho phép các giao dịch chỉ đọc).

Quá trình vận chuyển các tệp WAL từ tệp chính sang chế độ chờ được gọi là logshipping và có thể được thực hiện theo cách thủ công (các tập lệnh để rsync thay đổi từ $PGDATA/pg_wal của chính thư mục đến phụ) hoặc thông qua sao chép trực tuyến Các tính năng khác nhau như vị trí sao chép , phản hồi ở chế độ chờ chuyển đổi dự phòng đã được thêm vào theo thời gian để cải thiện độ tin cậy và tính hữu ích của việc sao chép trực tuyến.

Một “tính năng” lớn của sao chép phát trực tuyến là tất cả hoặc không có gì. Tất cả các thay đổi đối với tất cả các đối tượng từ tất cả các cơ sở dữ liệu trên cơ sở dữ liệu chính phải được chuyển đến chế độ chờ và chế độ chờ phải nhập mọi thay đổi. Không thể sao chép một cách đục khoét một phần cơ sở dữ liệu của bạn.

Sao chép logic

Sao chép lôgic , được thêm vào v10, giúp bạn có thể thực hiện điều đó –chỉ sao chép một tập hợp các bảng cho các máy chủ khác. Nó được giải thích riêng với một ví dụ. Hãy lấy một cơ sở dữ liệu có tên là src trong một máy chủ và tạo một bảng trong đó:

src=> CREATE TABLE t (col1 int, col2 int);
CREATE TABLE
src=> INSERT INTO t VALUES (1,10), (2,20), (3,30);
INSERT 0 3

Chúng tôi cũng sẽ tạo một ấn phẩm trong cơ sở dữ liệu này (lưu ý rằng bạn cần phải có đặc quyền của người dùng cấp cao để thực hiện việc này):

src=# CREATE PUBLICATION mypub FOR ALL TABLES;
CREATE PUBLICATION

Bây giờ chúng ta hãy đi đến một cơ sở dữ liệu dst trên một máy chủ khác và tạo một bảng tương tự:

dst=# CREATE TABLE t (col1 int, col2 int, col3 text NOT NULL DEFAULT 'foo');
CREATE TABLE

Và bây giờ chúng tôi thiết lập một đăng ký ở đây sẽ kết nối với ấn phẩm trên nguồn và bắt đầu thực hiện các thay đổi. (Lưu ý rằng bạn cần có một người dùng repuser tại máy chủ nguồn với các đặc quyền sao chép và quyền truy cập đọc vào các bảng.)

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

Các thay đổi được đồng bộ hóa và bạn có thể thấy các hàng ở phía đích:

dst=# SELECT * FROM t;
 col1 | col2 | col3
------+------+------
    1 |   10 | foo
    2 |   20 | foo
    3 |   30 | foo
(3 rows)

Bảng đích có thêm một cột “col3”, cột này không bị trùng lặp. Các thay đổi được sao chép "một cách hợp lý" - vì vậy, miễn là có thể chèn một hàng chỉ có t.col1 và t.col2, thì quá trình sao chép sẽ thực hiện được.

So với sao chép trực tuyến, tính năng sao chép hợp lý là hoàn hảo để sao chép, chẳng hạn như một lược đồ đơn lẻ hoặc một tập hợp các bảng trong một tập dữ liệu cụ thể đến một máy chủ khác.

Nhân rộng các Thay đổi về Giản đồ

Giả sử bạn có ứng dụng Django với tập hợp các bảng nằm trong cơ sở dữ liệu nguồn. Thật dễ dàng và hiệu quả khi thiết lập bản sao hợp lý để chuyển tất cả các bảng này vào một máy chủ khác, nơi bạn có thể chạy báo cáo, phân tích, công việc hàng loạt, ứng dụng hỗ trợ nhà phát triển / khách hàng và những thứ tương tự mà không cần chạm vào dữ liệu “thực” và không ảnh hưởng đến ứng dụng sản xuất.

Có thể hạn chế lớn nhất của Logical Replication hiện tại là nó không sao chép các thay đổi của giản đồ - bất kỳ lệnh DDL nào được thực thi tại các cơ sở dữ liệu nguồn sẽ không gây ra một thay đổi tương tự trong cơ sở dữ liệu đích, không giống như trong streamingreplication. Ví dụ:nếu chúng tôi thực hiện việc này tại cơ sở dữ liệu nguồn:

src=# ALTER TABLE t ADD newcol int;
ALTER TABLE
src=# INSERT INTO t VALUES (-1, -10, -100);
INSERT 0 1

điều này được ghi vào tệp nhật ký đích:

ERROR:  logical replication target relation "public.t" is missing some replicated columns

và sự sao chép dừng lại. Cột phải được thêm "theo cách thủ công" tại vị trí đích, tại thời điểm này, quá trình sao chép lại tiếp tục:

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)

Điều này có nghĩa là nếu ứng dụng Django của bạn đã thêm một tính năng mới cần các cột hoặc bảng mới và bạn phải chạy django-admin migrate trên cơ sở dữ liệu nguồn, quá trình thiết lập sao chép bị hỏng.

​​Giải pháp thay thế

Đặt cược tốt nhất của bạn để khắc phục sự cố này là tạm dừng đăng ký trên điểm đến, di chuyển điểm đến trước, sau đó đến nguồn và sau đó tiếp tục đăng ký. Bạn có thể tạm dừng và tiếp tục đăng ký như sau:

-- pause replication (destination side)
ALTER SUBSCRIPTION mysub DISABLE;

-- resume replication
ALTER SUBSCRIPTION mysub ENABLE;

Nếu các bảng mới được thêm vào và ấn phẩm của bạn không phải là “CHO TẤT CẢ CÁC BẢNG”, bạn sẽ cần thêm chúng vào ấn bản theo cách thủ công:

ALTER PUBLICATION mypub ADD TABLE newly_added_table;

Bạn cũng sẽ cần “làm mới” đăng ký ở phía đích để yêu cầuPostgres bắt đầu đồng bộ hóa các bảng mới:

dst=# ALTER SUBSCRIPTION mysub REFRESH PUBLICATION;
ALTER SUBSCRIPTION

Chuỗi

Hãy xem xét bảng này tại nguồn, có trình tự:

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)

Chuỗi s_a_seq được tạo để sao lưu a cột của serial type.Điều này tạo ra các giá trị tự động cộng gộp cho s.a . Bây giờ chúng ta hãy sao chép thisinto dst và chèn một hàng khác:

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)

Rất tiếc, chuyện gì vừa xảy ra vậy? Đích đã cố gắng bắt đầu chuỗi fromscratch và tạo giá trị 1 cho a . Điều này là do các bản sao lôgic không sao chép các giá trị cho các chuỗi vì giá trị tiếp theo của các chuỗi này không được lưu trữ trong chính bảng.

Giải pháp thay thế

Nếu nghĩ về điều này một cách hợp lý, bạn không thể sửa đổi cùng một giá trị “tự động tăng” từ hai nơi mà không có đồng bộ hóa hai chiều. Nếu bạn thực sự cần số hồi lưu trong mỗi hàng của bảng và cần chèn vào bảng đó từ nhiều máy chủ, bạn có thể:

  • sử dụng một nguồn bên ngoài cho số, như ZooKeeper hoặc etcd,
  • sử dụng các phạm vi không chồng chéo - ví dụ:máy chủ đầu tiên tạo và chèn các số trong phạm vi 1 đến 1 triệu, máy chủ thứ hai trong phạm vi 1 triệu đến 2 triệu, v.v.

Bảng không có hàng duy nhất

Hãy thử tạo một bảng không có khóa chính và sao chép nó:

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

Và các hàng hiện cũng đang ở trên đích:

dst=# SELECT * FROM nopk;
   foo
----------
 new york
 boston
(2 rows)

Bây giờ, hãy thử xóa hàng thứ hai tại nguồn:

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.

Điều này xảy ra vì đích sẽ không thể xác định duy nhất hàng cần được xóa (hoặc cập nhật) mà không có khóa chính.

Giải pháp thay thế

Tất nhiên, bạn có thể thay đổi lược đồ để bao gồm khóa chính. Trong trường hợp bạn không muốn làm điều đó, bạn ALTER TABLE và đặt "nhận dạng bản sao" thành toàn bộ hoặc một chỉ mục duy nhất. Ví dụ:

src=# ALTER TABLE nopk REPLICA IDENTITY FULL;
ALTER TABLE
src=# DELETE FROM nopk WHERE foo='boston';
DELETE 1

Việc xóa hiện thành công và quá trình nhân rộng:

dst=# SELECT * FROM nopk;
   foo
----------
 new york
(1 row)

Nếu bảng của bạn thực sự không có cách nào để xác định duy nhất các hàng, thì bạn đang gặp một chút khó khăn. Xem phần REPLICA IDENTITY của ALTERTABLE để biết thêm thông tin.

Các điểm đến được phân vùng khác nhau

Sẽ rất tuyệt khi có một nguồn được phân chia theo một cách và truy cập theo một cách khác phải không? Ví dụ:tại nguồn, chúng ta có thể xác định tỷ lệ cho mỗi tháng và tại điểm đến cho mỗi năm. Có lẽ thedestination là một cỗ máy lớn hơn và chúng ta cần lưu giữ dữ liệu lịch sử, nhưng hiếm khi cần dữ liệu đó.

Hãy tạo một bảng được phân vùng hàng tháng tại nguồn:

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

Và hãy thử tạo một bảng được phân vùng hàng năm tại đích:

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 phàn nàn rằng nó cần bảng phân vùng cho tháng 1 năm 2019, mà chúng tôi không có ý định tạo trên đích.

Điều này xảy ra bởi vì sao chép lôgic không hoạt động ở cấp bảng cơ sở mà ở cấp bảng con. Không có giải pháp thực sự nào cho việc này - nếu bạn đang sử dụng phân vùng, thì phân cấp phân vùng phải giống nhau ở cả hai phía của thiết lập sao chép alogical.

Đối tượng lớn

Các đối tượng lớn không thể được tái tạo bằng cách sử dụng sao chép hợp lý. Điều này có lẽ không phải là một vấn đề lớn ngày nay, vì lưu trữ các vật thể lớn không phải là một thực tế phổ biến thời hiện đại. Cũng dễ dàng hơn để lưu trữ một tham chiếu đến một đối tượng lớn trên một số lưu trữ bên ngoài, redudant (như NFS, S3, v.v.) và sao chép tham chiếu đó hơn là lưu trữ và sao chép chính đối tượng.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Các giải pháp DBaaS tốt nhất cho PostgreSQL

  2. Tính toán điểm cách 50 dặm (Bắc, 45% NE, 45% SW)

  3. Đếm số lần xuất hiện của một chuỗi con trong một chuỗi trong PostgreSQL

  4. Mẫu bảng và các phương pháp khác để lấy các bộ số ngẫu nhiên

  5. Kiểm tra PL / pgSQL nếu một hàng tồn tại