As you’ll be able to perceive from my earlier blogs I’m actually into PostgreSQL.
Beforehand we ran Debezium in Embedded mode. Behind the scenes, Debezium consumes the adjustments that have been dedicated to the transaction log. This occurs by using the logical decoding function of PostgreSQL.
On this weblog, we will deal with replication and extra particularly bidirectional replication. To attain bidirectional replication in PostgreSQL we’d like the module pglogical. You may surprise in regards to the distinction between logical decoding and pglogical. Basically, logical decoding has its origins in PgLocigal. View PgLocial as a extra featureful module whereas logical decoding is embedded in a PostgreSQL distribution.
We are going to create a customized PostgreSQL Docker picture and set up PgLogical.
# Use the official PostgreSQL picture as base
FROM postgres:15
USER root
RUN apt-get replace; apt-get set up postgresql-15-pglogical -y
USER postgres
Additionally, we have to have a PostgreSQL configuration that can allow PgLogical replication and battle decision.
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'
Let’s break this down. We added pglogical and we enabled track_commit_timestamp
. By enabling this parameter PostgreSQL tracks the commit time of transactions. This might be essential for the battle decision technique.
Now let’s see the battle decision. We chosen ‘first_update_wins’
; subsequently, in case of two transactions working on the identical row, the transaction that completed first would be the one to be thought-about.
Bidirectional replication is ready up upon a desk. Since we use Docker we will present an initialization script to PostgreSQL.
The script will:
- Allow pglogical
- Create the desk
- Add a goal node
- Insert the row we will run checks upon
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB"
Let’s create the cases now utilizing Docker Compose.
model: '3.1'
companies:
postgres-a:
construct: ./pglogicalimage
restart: at all times
surroundings:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
TARGET: postgres-b
volumes:
- ./config/postgresql.conf:/and many others/postgresql/postgresql.conf
- ./init:/docker-entrypoint-initdb.d
command:
- "-c"
- "config_file=/etc/postgresql/postgresql.conf"
ports:
- 5431:5432
postgres-b:
construct: ./pglogicalimage
restart: at all times
surroundings:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
TARGET: postgres-a
volumes:
- ./config/postgresql.conf:/and many others/postgresql/postgresql.conf
- ./init:/docker-entrypoint-initdb.d
command:
- "-c"
- "config_file=/etc/postgresql/postgresql.conf"
ports:
- 5432:5432
We will get our cases up and operating by issuing:
docker compose up
Docker Compose V2 is on the market with many good options, you could find extra about it within the e-book I authored: A Developer’s Important Information to Docker Compose.
Since each cases are up and operating we have to allow the replication. Subsequently we will subscribe the nodes to one another.
Execute on the primary node:
SELECT pglogical.create_subscription(
subscription_name := 'postgres_b',
provider_dsn := 'host=postgres-b port=5432 dbname=postgres consumer=postgres password=postgres',
synchronize_data := false,
forward_origins := '{}' );
Execute on the second node:
SELECT pglogical.create_subscription(
subscription_name := 'postgres_a',
provider_dsn := 'host=postgres-a port=5432 dbname=postgres consumer=postgres password=postgres',
synchronize_data := false,
forward_origins := '{}' );
You should use any PostgreSQL shopper that fits you. Alternatively, you’ll be able to simply use the psql shopper that comes packaged with the Docker Pictures.
For instance:
Login to the primary node:
docker compose exec postgres-a psql --username postgres --dbname postgres
Login to the second node:
docker compose
exec
postgres-b psql --username postgres --dbname postgres
Let’s see how battle decision will work now.
On the primary node, we will run the next snippet:
BEGIN;
UPDATE test_schema.worker SET lastname="first wins";
#earlier than committing begin transaction on postgres-b
COMMIT;
Don’t press commit instantly, as a substitute take the time and earlier than you commit the transaction begin the next transaction on the second node.
BEGIN;
UPDATE test_schema.worker SET lastname="second looses";
#be sure transaction on node postgres-a is dedicated first.
COMMIT;
This transaction might be dedicated after the transaction that takes place in postgres-a.
Let’s verify the logs on postgres-a-1:
postgres-a-1 | 2024-05-01 07:10:45.128 GMT [70] LOG: CONFLICT: distant UPDATE on relation test_schema.worker (native index employee_pkey). Decision: keep_local.
postgres-a-1 | 2024-05-01 07:10:45.128 GMT [70] DETAIL: current native tuple {id[int4]:1 firstname[text]:John lastname[text]:first wins e mail[text]:john1@doe.com age[int4]:18 wage[float4]:1234.23} xid=748,origin=0,timestamp=2024-05-01 07:10:42.269227+00; distant tuple {id[int4]:1 firstname[text]:John lastname[text]:second looses e mail[text]:john1@doe.com age[int4]:18 wage[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 distant relation test_schema.worker in commit earlier than 0/16181C0, xid 747 dedicated at 2024-05-01 07:10:45.125791+00 (motion #2) from node replorigin 1
The transaction that happened on postgres-a completed first. Postgres-a acquired the replication knowledge from the transaction of node postgres-b. A comparability was issued on the commit timestamp as a result of the commit timestamp of the transaction on postgres-a was earlier the decision was to maintain the native adjustments.
We will see the reverse on postgres-b:
postgres-b-1 | 2024-05-01 07:10:45.127 GMT [81] LOG: CONFLICT: distant UPDATE on relation test_schema.worker (native index employee_pkey). Decision: apply_remote.
postgres-b-1 | 2024-05-01 07:10:45.127 GMT [81] DETAIL: current native tuple {id[int4]:1 firstname[text]:John lastname[text]:second looses e mail[text]:john1@doe.com age[int4]:18 wage[float4]:1234.23} xid=747,origin=0,timestamp=2024-05-01 07:10:45.125791+00; distant tuple {id[int4]:1 firstname[text]:John lastname[text]:first wins e mail[text]:john1@doe.com age[int4]:18 wage[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 distant relation test_schema.worker in commit earlier than 0/1618488, xid 748 dedicated at 2024-05-01 07:10:42.269227+00 (motion #2) from node replorigin 1
Let’s verify the outcome within the database.
postgres=# SELECT*FROM test_schema.worker;
id | firstname | lastname | e mail | age | wage
----+-----------+------------+---------------+-----+---------
1 | John | first wins | john1@doe.com | 18 | 1234.23
As anticipated the primary transaction is the one which stayed.
To wrap it up:
- We began two transactions in parallel
- We modified the identical row
- We accepted the adjustments of the transaction that completed first
That’s it. Hope you had some enjoyable and now you may have one other software to your wants. Within the subsequent weblog, we will study PostgreSQL’s driver capabilities and the way we will configure an automatic failover to a different occasion.