Skip to content

Commit c0da05e

Browse files
committed
Complete dropForeignOrigin flag implementation and tests
The databases we listen to at Gadget see really huge transactions when we do shard moves. Shard moves use logical replication to move data from one database to another, and logical replication does a bigass COPY statement for each table at the start of replication to move the initial state of the table from the source database to the destination. These create huge transactions in the WAL which the wal-listener needs to process. We don't really care about this data downstream -- we in fact don't want it at all, as it is just a replay of data on a new shard that we've already seen on the old shard. Luckily, postgres has a property in the replication protocol that allows us to tell postgres that we don't actually want these transactions streamed to us in the wal-listener: the `ORIGIN` option of a subscription! When you create a subscription, you can specify that you only want messages with no origin, which means only messages that originate from the instance the subscription is on, instead of any upstream instances: ``` primary1=# CREATE SUBSCRIPTION sub_pri1_pri3 primary1-# CONNECTION 'dbname=foo host=primary3 user=repuser' primary1-# PUBLICATION pub_pri3 WITH (origin = NONE); ``` This matches what we want when using the wal-listener at Gadget: we want transactions from a data plane database for envs that currently live on that database. But, when an env is moving to that database, the transactions will have an origin of the upstream shard move source, and we don't want that data, as we're still actually serving the data from that source, and listening to it too. Sadly, this `ORIGIN` option is only supported on PG 16 and newer, and we were on PG 15 at the time, so we couldn't use it. This PR switches that around, as now we're on PG 17! Horray At the time though, we were persistent, and tried to make due with a different, lower level feature of the postgres logical replication protocol, where each incoming message includes an `origin` property that describes where it came from. We hoped we could inspect this property and quickly drop messages that had origins we didn't care about as well. We added the `dropForeignOrigin` configuration flag to the wal-listener to set this flag in #31. Even more sadly, we learned the hard way that while the `origin` property of was present in the older postgres versions, and valid in the protocol, it was just never set at all. Sad. So, this PR drops support for the old, busted, message-inspection based way of origin filtering, in favour of the better peforming, server-side filtering using the ORIGIN property of the subscription.
1 parent ae6101c commit c0da05e

8 files changed

Lines changed: 485 additions & 326 deletions

File tree

.github/workflows/test.yml

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,32 +59,58 @@ jobs:
5959
- name: Download dependencies
6060
run: go mod download
6161

62-
- name: Start PostgreSQL with logical replication
62+
- name: Create Docker network
63+
run: docker network create wal-test-network
64+
65+
- name: Start primary PostgreSQL (upstream)
6366
run: |
6467
docker run -d \
65-
--name postgres-test \
68+
--name postgres-primary \
69+
--network wal-test-network \
6670
-e POSTGRES_USER=postgres \
6771
-e POSTGRES_PASSWORD=postgres \
6872
-e POSTGRES_DB=postgres \
6973
-p 5432:5432 \
7074
postgres:17 \
7175
-c wal_level=logical \
72-
-c max_wal_senders=10 \
73-
-c max_replication_slots=10
76+
-c max_wal_senders=20 \
77+
-c max_replication_slots=20
78+
79+
- name: Start downstream PostgreSQL (for foreign origin testing)
80+
run: |
81+
docker run -d \
82+
--name postgres-downstream \
83+
--network wal-test-network \
84+
-e POSTGRES_USER=postgres \
85+
-e POSTGRES_PASSWORD=postgres \
86+
-e POSTGRES_DB=postgres \
87+
-p 5433:5432 \
88+
postgres:17 \
89+
-c wal_level=logical \
90+
-c max_wal_senders=20 \
91+
-c max_replication_slots=20
7492
75-
- name: Wait for PostgreSQL to be ready
93+
- name: Wait for PostgreSQL instances to be ready
7694
run: |
7795
for i in {1..30}; do
78-
docker exec postgres-test pg_isready -U postgres && break
79-
echo "Waiting for PostgreSQL..."
96+
docker exec postgres-primary pg_isready -U postgres && break
97+
echo "Waiting for primary PostgreSQL..."
98+
sleep 2
99+
done
100+
for i in {1..30}; do
101+
docker exec postgres-downstream pg_isready -U postgres && break
102+
echo "Waiting for downstream PostgreSQL..."
80103
sleep 2
81104
done
82105
83106
- name: Verify PostgreSQL configuration
84107
run: |
85-
docker exec postgres-test psql -U postgres -c "SHOW wal_level;"
86-
docker exec postgres-test psql -U postgres -c "SHOW max_replication_slots;"
87-
docker exec postgres-test psql -U postgres -c "SHOW max_wal_senders;"
108+
echo "=== Primary PostgreSQL ==="
109+
docker exec postgres-primary psql -U postgres -c "SHOW wal_level;"
110+
docker exec postgres-primary psql -U postgres -c "SHOW max_replication_slots;"
111+
echo "=== Downstream PostgreSQL ==="
112+
docker exec postgres-downstream psql -U postgres -c "SHOW wal_level;"
113+
docker exec postgres-downstream psql -U postgres -c "SHOW max_replication_slots;"
88114
89115
- name: Run integration tests
90116
env:
@@ -93,4 +119,9 @@ jobs:
93119
POSTGRES_USER: postgres
94120
POSTGRES_PASSWORD: postgres
95121
POSTGRES_DB: postgres
122+
POSTGRES_DOWNSTREAM_HOST: localhost
123+
POSTGRES_DOWNSTREAM_PORT: 5433
124+
# Internal hostnames for container-to-container communication (used by subscriptions)
125+
POSTGRES_PRIMARY_HOST_INTERNAL: postgres-primary
126+
POSTGRES_PRIMARY_PORT_INTERNAL: 5432
96127
run: go test -v -tags=integration -timeout=300s ./listener/...

docker/docker-compose.yml

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ x-postgres-common:
99
user: postgres
1010
restart: always
1111
healthcheck:
12-
test: 'pg_isready -U postgres --dbname=my_db'
12+
test: 'pg_isready -U postgres --dbname=postgres'
1313
interval: 10s
1414
timeout: 5s
1515
retries: 5
1616

1717
services:
18+
# Primary PostgreSQL - the "upstream" database
19+
# This is where we make changes that get replicated to the downstream
1820
postgres_primary:
1921
<<: *postgres-common
2022
ports:
@@ -29,30 +31,31 @@ services:
2931
postgres
3032
-c wal_level=logical
3133
-c hot_standby=on
32-
-c max_wal_senders=10
33-
-c max_replication_slots=10
34+
-c max_wal_senders=20
35+
-c max_replication_slots=20
3436
-c hot_standby_feedback=on
3537
volumes:
3638
- ./scripts:/docker-entrypoint-initdb.d
3739

38-
postgres_replica:
40+
# Downstream PostgreSQL - receives logical replication from primary
41+
# wal-listener connects here to test foreign origin filtering
42+
postgres_downstream:
3943
<<: *postgres-common
4044
ports:
4145
- 5433:5432
4246
environment:
43-
PGUSER: replicator
44-
PGPASSWORD: replicator_password
47+
POSTGRES_USER: postgres
48+
POSTGRES_DB: postgres
49+
POSTGRES_PASSWORD: postgres
50+
POSTGRES_HOST_AUTH_METHOD: "scram-sha-256\nhost replication all 0.0.0.0/0 md5"
51+
POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256"
4552
command: |
46-
bash -c "
47-
until pg_basebackup --pgdata=/var/lib/postgresql/data -R --slot=replication_slot --host=postgres_primary --port=5432
48-
do
49-
echo 'Waiting for primary to connect...'
50-
sleep 1s
51-
done
52-
echo 'Backup done, starting replica...'
53-
chmod 0700 /var/lib/postgresql/data
54-
postgres
55-
"
53+
postgres
54+
-c wal_level=logical
55+
-c hot_standby=on
56+
-c max_wal_senders=20
57+
-c max_replication_slots=20
58+
-c hot_standby_feedback=on
5659
depends_on:
5760
- postgres_primary
5861

0 commit comments

Comments
 (0)