This project demonstrates database-level replication between two PostgreSQL databases using its native publish/subscribe feature—no app logic or external tools like Kafka required! It simulates two services (source_service
and target_service
) where changes in one database (source_db
) automatically sync to another (target_db
). Perfect for services needing shared, real-time data without business logic overhead.
- How PostgreSQL’s logical replication works.
- Setting up replication between two databases in Docker.
- Using separate
docker-compose
files for service-style isolation. - Testing real-time data sync without writing code.
- Docker and Docker Compose installed.
- Basic terminal skills.
source_service/
: The publisher service withsource_db
.docker-compose.yml
: Runs PostgreSQL 17 with a publication.source-init.sql
: Creates theusers
table andusers_pub
publication.
target_service/
: The subscriber service withtarget_db
.docker-compose.yml
: Runs PostgreSQL 17 with a subscription.target-init.sql
: Sets up theusers
table and subscribes tosource_db
.
git clone https://github.com/thekubera/postgres-logical-replication
cd postgres-logical-replication
Both services need to talk over the same network:
docker network create replication_net
The publisher (source_db
) comes first:
cd source_service
docker compose up -d
- Creates a
users
table with initial data (Alice). - Sets up the
users_pub
publication.
The subscriber (target_db
) connects to source_db
:
cd ../target_service
docker compose up -d
- Creates a
users
table. - Subscribes to
users_pub
viausers_sub
.
Let’s see replication in action!
In source_db
:
docker exec -it source_service-source_db-1 psql -U postgres -d source_db -c "SELECT * FROM users;"
Output: Alice (ID 1).
In target_db
:
docker exec -it target_service-target_db-1 psql -U postgres -d target_db -c "SELECT * FROM users;"
Output: Alice (synced!).
Insert a row in source_db
:
docker exec -it source_service-source_db-1 psql -U postgres -d source_db -c "INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');"
Check target_db
:
docker exec -it target_service-target_db-1 psql -U postgres -d target_db -c "SELECT * FROM users;"
Output: Alice and Bob—magic!
Update in source_db
:
docker exec -it source_service-source_db-1 psql -U postgres -d source_db -c "UPDATE users SET name = 'Bob Updated' WHERE email = 'bob@example.com';"
Check target_db
:
docker exec -it target_service-target_db-1 psql -U postgres -d target_db -c "SELECT * FROM users;"
Output: Alice and Bob Updated.
- Publisher (
source_db
): Useswal_level=logical
and a publication (users_pub
) to stream changes from theusers
table. - Subscriber (
target_db
): Uses a subscription (users_sub
) to pull changes fromsource_db
over thereplication_net
network. - Docker: Runs each service in isolated containers, mimicking services on different servers.
- “Could not translate host name”: Ensure both services are on
replication_net
(docker network inspect replication_net
). - “Max WAL senders” error:
max_wal_senders
andmax_replication_slots
are set to 2 insource_service
to handle initial sync and ongoing replication. - No data in
target_db
: Check logs (docker compose logs
) and subscription status:
docker exec -it target_service-target_db-1 psql -U postgres -d target_db -c "SELECT * FROM pg_stat_subscription;"
cd source_service
docker compose down
cd ../target_service
docker compose down