Skip to content

Commit 237b30a

Browse files
committed
Added logical slot setting, pgbouncer switchover config, added lag monitoring, pgbouncer new reload logic with config exchange
1 parent f2921ca commit 237b30a

7 files changed

+142
-18
lines changed

configs/pgbouncer_new.ini

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[databases]
2+
testdb_rw = host=postgres_target port=5432 auth_user=testuser dbname=testdb
3+
testdb_ro = host=postgres_target_ro_1,postgres_target_ro_2 port=5432 auth_user=testuser dbname=testdb
4+
5+
[pgbouncer]
6+
listen_addr = 0.0.0.0
7+
listen_port = 6432
8+
unix_socket_dir =
9+
user = postgres
10+
auth_file = /etc/pgbouncer/userlist.txt
11+
auth_type = trust
12+
pool_mode = transaction
13+
max_client_conn = 1000
14+
default_pool_size = 100
15+
ignore_startup_parameters = extra_float_digits
16+
server_round_robin = 1
17+
max_prepared_statements = 1000
18+
19+
# Log settings
20+
admin_users = testuser
21+
22+
# Connection sanity checks, timeouts
23+
24+
# TLS settings
25+
26+
# Dangerous timeouts

configs/switchover-config.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@ database:
1717
name: testdb
1818
user: testuser
1919
password: testpass
20+
logical_slot: dms_slot
21+
logical_publication: dms_pub
22+
logical_subscription: dms_sub
2023
connectors:
2124
- name: postgres-connector
2225
slot_name: debezium
2326
publication_name: dbz_publication
2427
pgbouncer:
2528
config_file: pgbouncer.ini
29+
switchover_config_file: pgbouncer_new.ini
2630
admin_port: 6433
2731
admin_user: testuser # PgBouncer admin user
2832
admin_password: testpass # PgBouncer admin password

docker-compose.yml

+1
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ services:
159159
image: edoburu/pgbouncer:v1.23.1-p2
160160
volumes:
161161
- ./configs/pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini
162+
- ./configs/pgbouncer_new.ini:/etc/pgbouncer/pgbouncer_new.ini
162163
- ./configs/userlist.txt:/etc/pgbouncer/userlist.txt
163164
command: ["/usr/bin/pgbouncer", "/etc/pgbouncer/pgbouncer.ini"]
164165
ports:

lib/config.sh

+7-1
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,17 @@ load_config() {
3535
DB_NAME=$(yq e '.database.name' "$CONFIG_FILE")
3636
DB_USER=$(yq e '.database.user' "$CONFIG_FILE")
3737
DB_PASSWORD=$(yq e '.database.password' "$CONFIG_FILE")
38+
39+
DB_LOGICAL_SLOT=$(yq e '.database.logical_slot' "$CONFIG_FILE")
40+
DB_LOGICAL_SUB=$(yq e '.database.logical_subscription' "$CONFIG_FILE")
41+
DB_LOGICAL_PUB=$(yq e '.database.logical_publication' "$CONFIG_FILE")
3842

3943
KAFKA_CONNECT_URL=($(yq e '.kafka.connect_url' "$CONFIG_FILE"))
4044
CONNECTORS=($(yq e '.connectors[].name' "$CONFIG_FILE"))
4145

42-
PGBOUNCER_CONFIG="${PROJECT_ROOT}/configs/$(yq e '.pgbouncer.config_file' "$CONFIG_FILE")"
46+
PGBOUNCER_CONFIG=$(yq e '.pgbouncer.config_file' "$CONFIG_FILE")
47+
PGBOUNCER_SWITCHOVER_CONFIG=$(yq e '.pgbouncer.switchover_config_file' "$CONFIG_FILE")
48+
4349
PGBOUNCER_PORT=$(yq e '.pgbouncer.admin_port' "$CONFIG_FILE")
4450
PGBOUNCER_ADMIN_USER=$(yq e '.pgbouncer.admin_user' "$CONFIG_FILE")
4551
PGBOUNCER_ADMIN_PASSWORD=$(yq e '.pgbouncer.admin_password' "$CONFIG_FILE")

lib/pgbouncer.sh

+46-7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,44 @@ pause_pool() {
3636
return 1
3737
}
3838

39+
resume_pgbouncer() {
40+
log_warn "Resuming PgBouncer..."
41+
42+
paused_dbs=$(PGPASSWORD=${PGBOUNCER_ADMIN_PASSWORD} psql -h localhost \
43+
-p ${PGBOUNCER_PORT} \
44+
-U ${PGBOUNCER_ADMIN_USER} \
45+
-d ${PGBOUNCER_ADMIN_DB} \
46+
-c "SHOW DATABASES" | awk -F'|' '
47+
NR==1 {
48+
for (i=1; i<=NF; i++) {
49+
gsub(" ", "", $i)
50+
if ($i == "paused") paused_col = i
51+
if ($i == "name") name_col = i
52+
}
53+
}
54+
NR>2 {
55+
val = $paused_col
56+
gsub(" ", "", val)
57+
if (val == "1") {
58+
gsub(" ", "", $name_col)
59+
print $name_col
60+
}
61+
}')
62+
63+
if [ -n "$paused_dbs" ]; then
64+
for db in $paused_dbs; do
65+
echo "Resuming database: $db"
66+
PGPASSWORD=${PGBOUNCER_ADMIN_PASSWORD} psql -h localhost \
67+
-p ${PGBOUNCER_PORT} \
68+
-U ${PGBOUNCER_ADMIN_USER} \
69+
-d ${PGBOUNCER_ADMIN_DB} \
70+
-c "RESUME $db"
71+
done
72+
else
73+
echo "No paused databases found"
74+
fi
75+
}
76+
3977
resume_pool() {
4078
local pool_name=$1
4179
log_warn "Resuming PgBouncer pool ${pool_name}..."
@@ -53,17 +91,18 @@ update_pgbouncer_pool() {
5391
log_warn "Updating PgBouncer pool ${pool_name} to point to ${new_host}..."
5492
pause_pool "$pool_name"
5593

56-
awk -v pool="$pool_name" -v host="$new_host" '
57-
$1 == pool {sub(/host=[^ ]*/, "host=" host)} 1
58-
' "$PGBOUNCER_CONFIG" > "$PGBOUNCER_CONFIG.new" && \
59-
mv "$PGBOUNCER_CONFIG.new" "$PGBOUNCER_CONFIG"
60-
94+
PGPASSWORD=${PGBOUNCER_ADMIN_PASSWORD} psql -h localhost \
95+
-p ${PGBOUNCER_PORT} \
96+
-U ${PGBOUNCER_ADMIN_USER} \
97+
-d ${PGBOUNCER_ADMIN_DB} \
98+
-c "SET conffile = '/etc/pgbouncer/$PGBOUNCER_SWITCHOVER_CONFIG';"
99+
61100
PGPASSWORD=${PGBOUNCER_ADMIN_PASSWORD} psql -h localhost \
62101
-p ${PGBOUNCER_PORT} \
63102
-U ${PGBOUNCER_ADMIN_USER} \
64103
-d ${PGBOUNCER_ADMIN_DB} \
65104
-c "RELOAD;"
66-
105+
67106
resume_pool "$pool_name"
68107
}
69108

@@ -89,7 +128,7 @@ check_ro_pool_status() {
89128

90129
get_current_pool_host() {
91130
local pool_name=$1
92-
PGPASSWORD=${PGBOUNCER_ADMIN_PASSWORD} psql -h localhost \
131+
PGPASSWORD=${PGBOUNCER_ADMIN_PASSWORD} psql -h "$SOURCE_HOST" \
93132
-p ${PGBOUNCER_PORT} \
94133
-U ${PGBOUNCER_ADMIN_USER} \
95134
-d ${PGBOUNCER_ADMIN_DB} \

lib/replication.sh

+54-10
Original file line numberDiff line numberDiff line change
@@ -68,26 +68,26 @@ switch_replication_direction() {
6868

6969
# Drop subscription in target (old subscriber)
7070
PGPASSWORD=$DB_PASSWORD psql -h "$TARGET_HOST" -p "$TARGET_PORT" -U "$DB_USER" -d "$DB_NAME" -c \
71-
"DROP SUBSCRIPTION IF EXISTS dms_sub;"
71+
"DROP SUBSCRIPTION IF EXISTS $DB_LOGICAL_SUB;"
7272

7373
PGPASSWORD=$DB_PASSWORD psql -h "$TARGET_HOST" -p "$TARGET_PORT" -U "$DB_USER" -d "$DB_NAME" -c \
74-
"SELECT pg_create_logical_replication_slot('dms_slot', 'pgoutput');"
74+
"SELECT pg_create_logical_replication_slot('$DB_LOGICAL_SLOT', 'pgoutput');"
7575

7676
# Create publication in target (new publisher)
7777
PGPASSWORD=$DB_PASSWORD psql -h "$TARGET_HOST" -p "$TARGET_PORT" -U "$DB_USER" -d "$DB_NAME" -c "
78-
DROP PUBLICATION IF EXISTS dms_pub;
79-
CREATE PUBLICATION dms_pub FOR ALL TABLES;"
78+
DROP PUBLICATION IF EXISTS $DB_LOGICAL_PUB;
79+
CREATE PUBLICATION $DB_LOGICAL_PUB FOR ALL TABLES;"
8080

8181
# Drop old publication in source
8282
PGPASSWORD=$DB_PASSWORD psql -h "$SOURCE_HOST" -p "$SOURCE_PORT" -U "$DB_USER" -d "$DB_NAME" -c "
83-
DROP PUBLICATION IF EXISTS dms_pub;"
83+
DROP PUBLICATION IF EXISTS $DB_LOGICAL_PUB;"
8484

8585
# Create subscription in source (new subscriber)
8686
PGPASSWORD=$DB_PASSWORD psql -h "$SOURCE_HOST" -p "$SOURCE_PORT" -U "$DB_USER" -d "$DB_NAME" -c "
87-
CREATE SUBSCRIPTION dms_sub
87+
CREATE SUBSCRIPTION $DB_LOGICAL_SUB
8888
CONNECTION 'host=${TARGET_INTERNAL_NAME} port=5432 dbname=${DB_NAME} user=${DB_USER} password=${DB_PASSWORD}'
89-
PUBLICATION dms_pub
90-
WITH (copy_data = false, create_slot = false, slot_name = 'dms_slot');"
89+
PUBLICATION $DB_LOGICAL_PUB
90+
WITH (copy_data = false, create_slot = false, slot_name = '$DB_LOGICAL_SLOT');"
9191
}
9292

9393
wait_for_slots_catchup() {
@@ -106,7 +106,7 @@ wait_for_slots_catchup() {
106106
sleep 5
107107
return 0
108108
fi
109-
109+
110110
# Start checks in parallel
111111
for slot in "${slots_to_migrate[@]}"; do
112112
check_slot_lag "$slot" &
@@ -156,6 +156,32 @@ check_slot_lag() {
156156
return 1
157157
}
158158

159+
wait_for_logical_replica_sync() {
160+
log_info "Waiting for logical replica to catchup..."
161+
162+
local sent_lsn
163+
sent_lsn=$(PGPASSWORD=$DB_PASSWORD psql -h "$SOURCE_HOST" -p "$SOURCE_PORT" -U "$DB_USER" -d "$DB_NAME" -Atc "
164+
SELECT sent_lsn
165+
FROM pg_stat_replication
166+
WHERE application_name = '$DB_LOGICAL_SUB'")
167+
168+
# wait 5 secs at max for catchup
169+
for ((i=0; i<5; i++)); do
170+
local diff_bytes
171+
diff_bytes=$(PGPASSWORD=$DB_PASSWORD psql -h "$TARGET_HOST" -p "$TARGET_PORT" -U "$DB_USER" -d "$DB_NAME" -Atc "
172+
SELECT pg_wal_lsn_diff('$sent_lsn', received_lsn)
173+
FROM pg_stat_subscription
174+
WHERE subname = '$DB_LOGICAL_SUB'")
175+
176+
if [ "$diff_bytes" -le 0 ]; then
177+
log_info "Logical replica caught up"
178+
return 0
179+
fi
180+
sleep 1
181+
done
182+
return 1
183+
}
184+
159185
get_active_slots() {
160186
PGPASSWORD=$DB_PASSWORD psql -h "$SOURCE_HOST" -p "$SOURCE_PORT" -U "$DB_USER" -d "$DB_NAME" -tAc "
161187
SELECT slot_name,
@@ -167,7 +193,25 @@ get_active_slots() {
167193
discover_replication_mappings() {
168194
local mappings=()
169195
local unmapped=()
170-
196+
197+
# Display logical replica for upgrade settings
198+
echo -e "\nLogical Replication Configuration:"
199+
printf "%-25s %-30s\n" "Setting" "Value"
200+
echo "------------------------------------------------------------------------"
201+
printf "%-25s %-30s\n" "Logical Slot" "$DB_LOGICAL_SLOT"
202+
printf "%-25s %-30s\n" "Logical Subscription" "$DB_LOGICAL_SUB"
203+
printf "%-25s %-30s\n" "Logical Publication" "$DB_LOGICAL_PUB"
204+
205+
echo -e "\nProceed with these settings? [y/N] "
206+
read -r response
207+
208+
if [[ "$response" =~ ^[Yy]$ ]]; then
209+
echo "Proceeding with configuration..."
210+
else
211+
echo "Operation cancelled."
212+
return 1
213+
fi
214+
171215
while IFS='|' read -r slot lag; do
172216
slot=$(echo "$slot" | xargs)
173217
lag=$(echo "$lag" | xargs)

scripts/switchover.sh

+4
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ perform_switchover() {
5858

5959
local rw_pool_name=$(yq e '.pgbouncer.pools.read_write.name' "$CONFIG_FILE")
6060
pause_pool "$rw_pool_name"
61+
wait_for_logical_replica_sync
6162

6263
sync_sequences
6364

@@ -84,18 +85,21 @@ swap_roles() {
8485
local orig_source_port="$SOURCE_PORT"
8586
local -a orig_source_replicas=("${SOURCE_REPLICAS[@]}")
8687
local orig_source_internal="$SOURCE_INTERNAL_NAME"
88+
local orig_pgb_config="$PGBOUNCER_CONFIG"
8789

8890
# Swap source to target
8991
SOURCE_HOST="$TARGET_HOST"
9092
SOURCE_PORT="$TARGET_PORT"
9193
SOURCE_REPLICAS=("${TARGET_REPLICAS[@]}")
9294
SOURCE_INTERNAL_NAME="$TARGET_INTERNAL_NAME"
95+
PGBOUNCER_CONFIG="$PGBOUNCER_SWITCHOVER_CONFIG"
9396

9497
# Swap target to original source
9598
TARGET_HOST="$orig_source_host"
9699
TARGET_PORT="$orig_source_port"
97100
TARGET_REPLICAS=("${orig_source_replicas[@]}")
98101
TARGET_INTERNAL_NAME="$orig_source_internal"
102+
PGBOUNCER_SWITCHOVER_CONFIG="$orig_pgb_config"
99103

100104
log_info "Roles swapped: new source=${SOURCE_HOST}, new target=${orig_source_host}"
101105
}

0 commit comments

Comments
 (0)