refactor: unify pglogical region schema (#498)

This commit is contained in:
shenlan 2025-10-13 01:30:28 +08:00 committed by GitHub
parent 5de1e32dd4
commit 5678975cb8
5 changed files with 135 additions and 124 deletions

View File

@ -4,8 +4,7 @@ PORT ?= 8080
OS := $(shell uname -s)
SCHEMA_FILE := ./sql/schema.sql
PGLOGICAL_SCHEMA_FILE := ./sql/schema_pglogical_init.sql
PGLOGICAL_REGION_CN_SCHEMA_FILE := ./sql/schema_pglogical_region_cn.sql
PGLOGICAL_REGION_GLOBAL_SCHEMA_FILE := ./sql/schema_pglogical_region_global.sql
PGLOGICAL_REGION_SCHEMA_FILE := ./sql/schema_pglogical_region.sql
MIGRATION_FILES := $(shell ls -1 sql/migrations/*.up.sql 2>/dev/null | sort)
@ -28,7 +27,7 @@ SUPERADMIN_EMAIL ?= admin@svc.plus
export PATH := /usr/local/go/bin:$(PATH)
.PHONY: all init init-go init-db init-pglogical-region-cn init-pglogical-region-global migrate-db dump-schema build start stop restart clean help dev test create-super-admin account-export account-import
.PHONY: all init init-go init-db init-pglogical-region init-pglogical-region-cn init-pglogical-region-global migrate-db dump-schema build start stop restart clean help dev test create-super-admin account-export account-import
all: build
@ -77,49 +76,65 @@ init-db:
fi; \
fi
init-pglogical-region:
@if [ -z "$(strip $(REGION_DB_URL))" ]; then \
echo "请通过 REGION_DB_URL 指定节点数据库连接字符串"; \
exit 1; \
fi
@if [ -z "$(strip $(NODE_NAME))" ]; then \
echo "请通过 NODE_NAME 指定当前节点名称"; \
exit 1; \
fi
@if [ -z "$(strip $(NODE_DSN))" ]; then \
echo "请通过 NODE_DSN 指定当前节点 DSN"; \
exit 1; \
fi
@if [ -z "$(strip $(SUBSCRIPTION_NAME))" ]; then \
echo "请通过 SUBSCRIPTION_NAME 指定订阅名称"; \
exit 1; \
fi
@if [ -z "$(strip $(PROVIDER_DSN))" ]; then \
echo "请通过 PROVIDER_DSN 指定 Provider DSN"; \
exit 1; \
fi
@if [ ! -f $(PGLOGICAL_REGION_SCHEMA_FILE) ]; then \
echo "未找到 pglogical schema 文件: $(PGLOGICAL_REGION_SCHEMA_FILE)"; \
exit 1; \
fi
@if ! command -v psql >/dev/null 2>&1; then \
echo "未检测到 psql请先安装 PostgreSQL 客户端"; \
exit 1; \
fi
@echo "使用数据库连接: $(REGION_DB_URL)"
@if psql "$(REGION_DB_URL)" -Atc "SELECT rolsuper FROM pg_roles WHERE rolname = current_user" | grep -qx 't'; then \
psql "$(REGION_DB_URL)" -v ON_ERROR_STOP=1 \
-v NODE_NAME="$(NODE_NAME)" \
-v NODE_DSN="$(NODE_DSN)" \
-v SUBSCRIPTION_NAME="$(SUBSCRIPTION_NAME)" \
-v PROVIDER_DSN="$(PROVIDER_DSN)" \
-f $(PGLOGICAL_REGION_SCHEMA_FILE); \
else \
echo "跳过 pglogical 初始化: 当前数据库用户缺少超级用户权限"; \
echo "提示: 请使用具有超级用户权限的连接或由管理员预先创建 pglogical 扩展"; \
fi
init-pglogical-region-cn:
@echo ">>> 初始化 CN 节点 pglogical 配置"
@if [ -z "$(strip $(REGION_CN_DB_URL))" ]; then \
echo "请通过 REGION_CN_DB_URL 指定 CN 节点数据库连接字符串"; \
exit 1; \
fi
@if [ ! -f $(PGLOGICAL_REGION_CN_SCHEMA_FILE) ]; then \
echo "未找到 pglogical schema 文件: $(PGLOGICAL_REGION_CN_SCHEMA_FILE)"; \
exit 1; \
fi
@if ! command -v psql >/dev/null 2>&1; then \
echo "未检测到 psql请先安装 PostgreSQL 客户端"; \
exit 1; \
fi
@echo "使用数据库连接: $(REGION_CN_DB_URL)"
@if psql "$(REGION_CN_DB_URL)" -Atc "SELECT rolsuper FROM pg_roles WHERE rolname = current_user" | grep -qx 't'; then \
psql "$(REGION_CN_DB_URL)" -v ON_ERROR_STOP=1 -f $(PGLOGICAL_REGION_CN_SCHEMA_FILE); \
else \
echo "跳过 pglogical 初始化: 当前数据库用户缺少超级用户权限"; \
echo "提示: 请使用具有超级用户权限的连接或由管理员预先创建 pglogical 扩展"; \
fi
@echo ">>> 初始化 CN 节点 pglogical 配置"
@$(MAKE) init-pglogical-region \
REGION_DB_URL="$(REGION_CN_DB_URL)" \
NODE_NAME="node_cn" \
NODE_DSN="host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx" \
SUBSCRIPTION_NAME="sub_from_global" \
PROVIDER_DSN="host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxx"
init-pglogical-region-global:
@echo ">>> 初始化 Global 节点 pglogical 配置"
@if [ -z "$(strip $(REGION_GLOBAL_DB_URL))" ]; then \
echo "请通过 REGION_GLOBAL_DB_URL 指定 Global 节点数据库连接字符串"; \
exit 1; \
fi
@if [ ! -f $(PGLOGICAL_REGION_GLOBAL_SCHEMA_FILE) ]; then \
echo "未找到 pglogical schema 文件: $(PGLOGICAL_REGION_GLOBAL_SCHEMA_FILE)"; \
exit 1; \
fi
@if ! command -v psql >/dev/null 2>&1; then \
echo "未检测到 psql请先安装 PostgreSQL 客户端"; \
exit 1; \
fi
@echo "使用数据库连接: $(REGION_GLOBAL_DB_URL)"
@if psql "$(REGION_GLOBAL_DB_URL)" -Atc "SELECT rolsuper FROM pg_roles WHERE rolname = current_user" | grep -qx 't'; then \
psql "$(REGION_GLOBAL_DB_URL)" -v ON_ERROR_STOP=1 -f $(PGLOGICAL_REGION_GLOBAL_SCHEMA_FILE); \
else \
echo "跳过 pglogical 初始化: 当前数据库用户缺少超级用户权限"; \
echo "提示: 请使用具有超级用户权限的连接或由管理员预先创建 pglogical 扩展"; \
fi
@echo ">>> 初始化 Global 节点 pglogical 配置"
@$(MAKE) init-pglogical-region \
REGION_DB_URL="$(REGION_GLOBAL_DB_URL)" \
NODE_NAME="node_global" \
NODE_DSN="host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx" \
SUBSCRIPTION_NAME="sub_from_cn" \
PROVIDER_DSN="host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx"
# =========================================
# migrate-db target

View File

@ -38,13 +38,48 @@ GRANT USAGE ON SCHEMA pglogical TO shenlan;
\q
⚙️ 执行顺序建议
步骤 节点 脚本 说明
1⃣ Global schema_base_bidirectional_enhanced.sql 创建业务结构(含 version/origin_node
2⃣ CN schema_base_bidirectional_enhanced.sql 创建相同业务结构
3⃣ Global schema_pglogical_region_global.sql 定义 Global provider + 订阅 CN
4⃣ CN schema_pglogical_region_cn.sql 定义 CN provider + 订阅 Global
💡 CN 节点执行 `schema_pglogical_region_cn.sql``make init-pglogical-region-cn` 前,请确保连接用户拥有 PostgreSQL 超级用户权限。
| 步骤 | 节点 | 脚本 / 命令 | 说明 |
| --- | --- | --- | --- |
| 1⃣ | Global | schema_base_bidirectional_enhanced.sql | 创建业务结构(含 version/origin_node |
| 2⃣ | CN | schema_base_bidirectional_enhanced.sql | 创建相同业务结构 |
| 3⃣ | Global | schema_pglogical_region.sql + 参数 | 定义 Global provider + 订阅 CN |
| 4⃣ | CN | schema_pglogical_region.sql + 参数 | 定义 CN provider + 订阅 Global |
💡 执行 `schema_pglogical_region.sql` 或对应的 `make init-pglogical-region-*` 目标前,请确保连接用户拥有 PostgreSQL 超级用户权限。
### 手动执行模版脚本
使用相同的 `schema_pglogical_region.sql` 模版即可初始化 Global 与 CN 两个节点,只需传入不同的变量:
```bash
# Global 节点示例
psql "$REGION_GLOBAL_DB_URL" -v ON_ERROR_STOP=1 \
-v NODE_NAME=node_global \
-v NODE_DSN='host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx' \
-v SUBSCRIPTION_NAME=sub_from_cn \
-v PROVIDER_DSN='host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx' \
-f account/sql/schema_pglogical_region.sql
# CN 节点示例
psql "$REGION_CN_DB_URL" -v ON_ERROR_STOP=1 \
-v NODE_NAME=node_cn \
-v NODE_DSN='host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx' \
-v SUBSCRIPTION_NAME=sub_from_global \
-v PROVIDER_DSN='host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxx' \
-f account/sql/schema_pglogical_region.sql
```
也可以通过新的 `make init-pglogical-region` 目标自定义变量,例如:
```bash
make init-pglogical-region \
REGION_DB_URL="$REGION_DB_URL" \
NODE_NAME=node_example \
NODE_DSN="host=example port=5432 dbname=account user=pglogical password=secret" \
SUBSCRIPTION_NAME=sub_from_peer \
PROVIDER_DSN="host=peer port=5432 dbname=account user=pglogical password=secret"
```
- 若使用业务账号(如 `shenlan`执行初始化PostgreSQL 会提示缺少超级用户权限并跳过 `pglogical` 初始化。
- 建议改用 `postgres` 等超级用户连接执行,或由管理员预先安装 `pglogical` 扩展并授予业务用户访问权限。

View File

@ -1,9 +1,35 @@
-- =========================================
-- schema_pglogical_region_cn.sql
-- pglogical configuration for CN node
-- schema_pglogical_region.sql
-- pglogical configuration template for regional nodes
-- PostgreSQL 16+, 双向复制 (provider + subscriber)
-- 使用前请通过 psql -v NODE_NAME=... -v NODE_DSN=... -v SUBSCRIPTION_NAME=... -v PROVIDER_DSN=...
-- 提供所需参数。
-- =========================================
\if :{?NODE_NAME}
\else
\echo 'ERROR: 未设置 NODE_NAME 变量。请通过 -v NODE_NAME=... 传入节点名称。'
\quit 1
\endif
\if :{?NODE_DSN}
\else
\echo 'ERROR: 未设置 NODE_DSN 变量。请通过 -v NODE_DSN=... 传入当前节点 DSN。'
\quit 1
\endif
\if :{?SUBSCRIPTION_NAME}
\else
\echo 'ERROR: 未设置 SUBSCRIPTION_NAME 变量。请通过 -v SUBSCRIPTION_NAME=... 传入订阅名称。'
\quit 1
\endif
\if :{?PROVIDER_DSN}
\else
\echo 'ERROR: 未设置 PROVIDER_DSN 变量。请通过 -v PROVIDER_DSN=... 传入 Provider DSN。'
\quit 1
\endif
-- 🏗️ 确保 pglogical schema 及扩展存在
DO $$
BEGIN
@ -20,13 +46,13 @@ CREATE EXTENSION IF NOT EXISTS pglogical WITH SCHEMA pglogical;
-- 🧭 清理旧节点(可安全重入)
DO $$
BEGIN
PERFORM pglogical.drop_subscription('sub_from_global', true);
PERFORM pglogical.drop_subscription(:'SUBSCRIPTION_NAME', true);
EXCEPTION WHEN others THEN NULL;
END $$;
DO $$
BEGIN
PERFORM pglogical.drop_node('node_cn');
PERFORM pglogical.drop_node(:'NODE_NAME');
EXCEPTION WHEN others THEN NULL;
END $$;
@ -34,8 +60,8 @@ END $$;
-- 创建本节点 (Provider)
-- =========================================
SELECT pglogical.create_node(
node_name := 'node_cn',
dsn := 'host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx'
node_name := :'NODE_NAME',
dsn := :'NODE_DSN'
);
-- =========================================
@ -45,11 +71,11 @@ SELECT pglogical.create_replication_set('rep_all');
SELECT pglogical.replication_set_add_all_tables('rep_all', ARRAY['public']);
-- =========================================
-- 创建订阅 (订阅 Global 节点)
-- 创建订阅 (订阅远端节点)
-- =========================================
SELECT pglogical.create_subscription(
subscription_name := 'sub_from_global',
provider_dsn := 'host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxx',
subscription_name := :'SUBSCRIPTION_NAME',
provider_dsn := :'PROVIDER_DSN',
replication_sets := ARRAY['rep_all'],
synchronize_structure := false,
synchronize_data := true,

View File

@ -1,65 +0,0 @@
-- =========================================
-- schema_pglogical_region_global.sql
-- pglogical configuration for GLOBAL node
-- PostgreSQL 16+, 双向复制 (provider + subscriber)
-- =========================================
-- 🏗️ 确保 pglogical schema 及扩展存在
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_namespace WHERE nspname = 'pglogical'
) THEN
EXECUTE format('CREATE SCHEMA pglogical AUTHORIZATION %I', current_user);
END IF;
END;
$$;
CREATE EXTENSION IF NOT EXISTS pglogical WITH SCHEMA pglogical;
-- 🧭 清理旧节点(可安全重入)
DO $$
BEGIN
PERFORM pglogical.drop_subscription('sub_from_cn', true);
EXCEPTION WHEN others THEN NULL;
END $$;
DO $$
BEGIN
PERFORM pglogical.drop_node('node_global');
EXCEPTION WHEN others THEN NULL;
END $$;
-- =========================================
-- 创建本节点 (Provider)
-- =========================================
SELECT pglogical.create_node(
node_name := 'node_global',
dsn := 'host=global-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx'
);
-- =========================================
-- 定义复制集
-- =========================================
SELECT pglogical.create_replication_set('rep_all');
SELECT pglogical.replication_set_add_all_tables('rep_all', ARRAY['public']);
-- =========================================
-- 创建订阅 (订阅 CN 节点)
-- =========================================
SELECT pglogical.create_subscription(
subscription_name := 'sub_from_cn',
provider_dsn := 'host=cn-homepage.svc.plus port=5432 dbname=account user=pglogical password=xxxx',
replication_sets := ARRAY['rep_all'],
synchronize_structure := false,
synchronize_data := true,
forward_origins := '{}'
);
-- =========================================
-- 验证状态
-- =========================================
-- 运行以下命令检查同步是否正常:
-- SELECT * FROM pglogical.show_subscription_status();
-- 若 status = 'replicating' 表示复制成功。
-- =========================================

View File

@ -4,7 +4,7 @@
# 用于清理指定文件的历史提交记录,但保留当前版本
#
# 使用示例:
# ./clean_git_history.sh account/sql/schema_pglogical_region_cn.sql account/sql/schema_pglogical_region_global.sql
# ./clean_git_history.sh account/sql/schema_pglogical_region.sql
#
set -euo pipefail