PostgreSQL雙向複製教程

banq發表於2024-07-03

在本篇博文中,我們將重點介紹複製和更具體的雙向複製。要在 PostgreSQL 中實現雙向複製,我們需要模組 pglogical。您可能想知道邏輯解碼和 pglogical 之間的區別。

本質上,邏輯解碼起源於PgLocigal。將 PgLocial 視為功能更強大的模組,而邏輯解碼嵌入到 PostgreSQL 發行版中。

我們將建立自定義 PostgreSQL Docker 映像並安裝 PgLogical。

# Use the official PostgreSQL image as base
FROM postgres:15
USER root
RUN apt-get update; apt-get install postgresql-15-pglogical -y
USER postgres


我們還需要一個 PostgreSQL 配置,以啟用 PgLogical 複製和衝突解決。

listen_addresses = '*'
port = 5432
max_connections = 20
shared_buffers = 128MB
temp_buffers = 8MB
work_mem = 4MB
wal_level = logical
max_wal_senders = 3
track_commit_timestamp = on
shared_preload_libraries = 'pglogical'
pglogical.conflict_resolution = 'first_update_wins'


讓我們來分析一下。我們新增了 pglogical 並啟用了 track_commit_timestamp。透過啟用此引數,PostgreSQL 會跟蹤事務的提交時間。這對於衝突解決策略至關重要。

現在讓我們看看衝突解決。我們選擇了“first_update_wins”,因此如果兩個事務在同一行上操作,則將考慮最先完成的事務。

在表上設定雙向複製。由於我們使用Docker,因此我們將為 PostgreSQL 提供初始化指令碼。

該指令碼將:

  • 啟用 pglogical
  • 建立表
  • 新增目標節點
  • 插入要進行測試的行

#!/bin/bash
set -e
 
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
  ALTER SYSTEM RESET shared_preload_libraries;
  CREATE EXTENSION pglogical;
 
  create schema test_schema;
  create table test_schema.employee(
          id  SERIAL PRIMARY KEY,
          firstname   TEXT    NOT NULL,
          lastname    TEXT    NOT NULL,
          email       TEXT    not null,
          age         INT     NOT NULL,
          salary         real,
          unique(email)
      );
 
  SELECT pglogical.create_node(
      node_name := '$TARGET',
      dsn := 'host=$TARGET port=5432 dbname=$POSTGRES_DB user=$POSTGRES_USER password=$POSTGRES_PASSWORD');
 
  SELECT pglogical.replication_set_add_table('default', 'test_schema.employee', true);
 
  insert into test_schema.employee (id,firstname,lastname,email,age,salary) values (1,'John','Doe 1','john1@doe.com',18,1234.23);

EOSQL
 

 
現在讓我們使用 docker compose 建立例項。

version: '3.1'
 
services:
  postgres-a:
    build: ./pglogicalimage
    restart: always
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      TARGET: postgres-b
    volumes:
      - ./config/postgresql.conf:/etc/postgresql/postgresql.conf
      - ./init:/docker-entrypoint-initdb.d
    command:
      - "-c"
      - "config_file=/etc/postgresql/postgresql.conf"
    ports:
      - 5431:5432
  postgres-b:
    build: ./pglogicalimage
    restart: always
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      TARGET: postgres-a
    volumes:
      - ./config/postgresql.conf:/etc/postgresql/postgresql.conf
      - ./init:/docker-entrypoint-initdb.d
    command:
      - "-c"
      - "config_file=/etc/postgresql/postgresql.conf"
    ports:
      - 5432:5432

我們可以透過執行以下命令來啟動並執行例項

docker compose

由於兩個例項都已啟動並正在執行,因此我們需要啟用複製。因此,我們將節點相互訂閱。

在第一個節點上執行

SELECT pglogical.create_subscription(
  subscription_name := 'postgres_b',
  provider_dsn := 'host=postgres-b port=5432 dbname=postgres user=postgres password=postgres',
  synchronize_data := false,
  forward_origins := '{}' );

在第二個節點執行

SELECT pglogical.create_subscription(
  subscription_name := 'postgres_a',
  provider_dsn := 'host=postgres-a port=5432 dbname=postgres user=postgres password=postgres',
  synchronize_data := false,
  forward_origins := '{}' );

您可以使用任何適合您的 PostgreSQL 客戶端。

或者,您也可以使用 Docker 映象中附帶的 psql 客戶端。

例如:

登入第一個節點


docker compose exec postgres-a psql  --username postgres --dbname postgres

登入第二個節點

docker compose exec postgres-b psql  --username postgres --dbname postgres

現在讓我們看看衝突解決如何進行。

在第一個節點上,我們將執行以下程式碼片段


BEGIN;
UPDATE test_schema.employee SET lastname='first wins';
 
before committing start transaction on postgres-b
 
COMMIT;

不要立即按提交,而是花點時間在提交事務之前在第二個節點上啟動以下事務。


BEGIN;
UPDATE test_schema.employee SET lastname='second looses';
 
make sure transaction on node postgres-a is committed first.
 
COMMIT;

該事務將在 postgres-a 中發生的事務之後提交。

讓我們檢查一下 postgres-a-1 上的日誌

postgres-a-1  | 2024-05-01 07:10:45.128 GMT [70] LOG:  CONFLICT: remote UPDATE on relation test_schema.employee (local index employee_pkey). Resolution: keep_local.
postgres-a-1  | 2024-05-01 07:10:45.128 GMT [70] DETAIL:  existing local tuple {id[int4]:1 firstname[text]:John lastname[text]:first wins email[text]:john1@doe.com age[int4]:18 salary[float4]:1234.23} xid=748,origin=0,timestamp=2024-05-01 07:10:42.269227+00; remote tuple {id[int4]:1 firstname[text]:John lastname[text]:second looses email[text]:john1@doe.com age[int4]:18 salary[float4]:1234.23} in xact origin=1,timestamp=2024-05-01 07:10:45.125791+00,commit_lsn=0/16181C0
postgres-a-1  | 2024-05-01 07:10:45.128 GMT [70] CONTEXT:  apply UPDATE from remote relation test_schema.employee in commit before 0/16181C0, xid 747 committed at 2024-05-01 07:10:45.125791+00 (action #2) from node replorigin 1

  • postgres-a 上發生的事務首先完成。
  • Postgres-a 從節點 postgres-b 的事務接收了複製資料。
  • 對提交時間戳進行了比較,因為 postgres-a 上事務的提交時間戳較早,

因此解決方案是保留本地更改。

我們可以在 postgres-b 上看到反向

postgres-b-1 | 2024-05-01 07:10:45.127 GMT [81] LOG: CONFLICT: remote UPDATE on relation test_schema.employee (local index employee_pkey). Resolution: apply_remote.
postgres-b-1 | 2024-05-01 07:10:45.127 GMT [81] DETAIL: existing local tuple {id[int4]:1 firstname[text]:John lastname[text]:second looses email[text]:john1@doe.com age[int4]:18 salary[float4]:1234.23} xid=747,origin=0,timestamp=2024-05-01 07:10:45.125791+00; remote tuple {id[int4]:1 firstname[text]:John lastname[text]:first wins email[text]:john1@doe.com age[int4]:18 salary[float4]:1234.23} in xact origin=1,timestamp=2024-05-01 07:10:42.269227+00,commit_lsn=0/1618488
postgres-b-1 | 2024-05-01 07:10:45.127 GMT [81] CONTEXT: apply UPDATE from remote relation test_schema.employee in commit before 0/1618488, xid 748 committed at 2024-05-01 07:10:42.269227+00 (action #2) from node replorigin 1

讓我們在資料庫中檢查一下結果。

postgres=# SELECT*FROM test_schema.employee;
 id | firstname |  lastname  |     email     | age | salary  
----+-----------+------------+---------------+-----+---------
  1 | John      | first wins | john1@doe.com |  18 | 1234.23

正如預期的那樣,第一筆事務被保留了下來。

總結一下:

  • 我們同時開始了兩項事務
  • 我們改變了同一行
  • 我們接受了最先完成的事務的變更

就是這樣。希望您玩得開心,現在您有了另一個滿足您需求的工具。

相關文章