chore: normalize tabs in account makefile (#503)
This commit is contained in:
parent
c3fdbdc07d
commit
d37a45783a
@ -14,6 +14,8 @@ DB_HOST := 127.0.0.1
|
||||
DB_PORT := 5432
|
||||
DB_URL := postgres://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable
|
||||
|
||||
REPLICATION_MODE ?= pgsync
|
||||
|
||||
# 管理员账号用于执行需要超级权限的操作,例如删除数据库和初始化 pglogical
|
||||
# 默认为业务账号,可通过环境变量覆盖
|
||||
DB_ADMIN_USER ?= $(DB_USER)
|
||||
@ -21,6 +23,7 @@ DB_ADMIN_PASS ?= $(DB_PASS)
|
||||
|
||||
SCHEMA_FILE := ./sql/schema.sql
|
||||
PGLOGICAL_INIT_FILE := ./sql/schema_pglogical_init.sql
|
||||
PGLOGICAL_PATCH_FILE := ./sql/schema_pglogical_patch.sql
|
||||
PGLOGICAL_REGION_FILE := ./sql/schema_pglogical_region.sql
|
||||
|
||||
ACCOUNT_EXPORT_FILE ?= account-export.yaml
|
||||
@ -43,6 +46,7 @@ all: build
|
||||
help:
|
||||
@echo "🧭 XControl Account Service Makefile"
|
||||
@echo "make init 初始化 Go 环境与数据库"
|
||||
@echo "make init-db 执行数据库 schema(支持 REPLICATION_MODE=pgsync|pglogical)"
|
||||
@echo "make migrate-db 执行数据库迁移"
|
||||
@echo "make dump-schema 导出数据库 schema"
|
||||
@echo "make account-export 导出账号数据为 YAML"
|
||||
@ -74,17 +78,25 @@ init-db:
|
||||
@echo ">>> 初始化数据库 schema"
|
||||
@command -v psql >/dev/null || (echo "❌ 未检测到 psql,请安装 PostgreSQL 客户端" && exit 1)
|
||||
@psql "$(DB_URL)" -v ON_ERROR_STOP=1 -f $(SCHEMA_FILE)
|
||||
@if [ -f $(PGLOGICAL_INIT_FILE) ]; then \
|
||||
echo ">>> 初始化 pglogical schema"; \
|
||||
if PGPASSWORD="$(DB_ADMIN_PASS)" psql -h $(DB_HOST) -U $(DB_ADMIN_USER) -d $(DB_NAME) \
|
||||
-Atc "SELECT rolsuper FROM pg_roles WHERE rolname = current_user" 2>/dev/null | grep -qx 't'; then \
|
||||
PGPASSWORD="$(DB_ADMIN_PASS)" psql -h $(DB_HOST) -U $(DB_ADMIN_USER) -d $(DB_NAME) \
|
||||
-v ON_ERROR_STOP=1 -f $(PGLOGICAL_INIT_FILE); \
|
||||
elif psql "$(DB_URL)" -Atc "SELECT rolsuper FROM pg_roles WHERE rolname = current_user" | grep -qx 't'; then \
|
||||
psql "$(DB_URL)" -v ON_ERROR_STOP=1 -f $(PGLOGICAL_INIT_FILE); \
|
||||
else \
|
||||
echo "⚠️ 当前用户非超级用户,跳过 pglogical 初始化"; \
|
||||
@if [ "$(REPLICATION_MODE)" = "pglogical" ]; then \
|
||||
if [ -f $(PGLOGICAL_INIT_FILE) ]; then \
|
||||
echo ">>> 初始化 pglogical schema (REPLICATION_MODE=pglogical)"; \
|
||||
if PGPASSWORD="$(DB_ADMIN_PASS)" psql -h $(DB_HOST) -U $(DB_ADMIN_USER) -d $(DB_NAME) \
|
||||
-Atc "SELECT rolsuper FROM pg_roles WHERE rolname = current_user" 2>/dev/null | grep -qx 't'; then \
|
||||
PGPASSWORD="$(DB_ADMIN_PASS)" psql -h $(DB_HOST) -U $(DB_ADMIN_USER) -d $(DB_NAME) \
|
||||
-v ON_ERROR_STOP=1 -f $(PGLOGICAL_INIT_FILE); \
|
||||
elif psql "$(DB_URL)" -Atc "SELECT rolsuper FROM pg_roles WHERE rolname = current_user" | grep -qx 't'; then \
|
||||
psql "$(DB_URL)" -v ON_ERROR_STOP=1 -f $(PGLOGICAL_INIT_FILE); \
|
||||
else \
|
||||
echo "⚠️ 当前用户非超级用户,跳过 pglogical 初始化"; \
|
||||
fi; \
|
||||
fi; \
|
||||
if [ -f $(PGLOGICAL_PATCH_FILE) ]; then \
|
||||
echo ">>> 应用 pglogical 默认值补丁"; \
|
||||
psql "$(DB_URL)" -v ON_ERROR_STOP=1 -f $(PGLOGICAL_PATCH_FILE); \
|
||||
fi; \
|
||||
else \
|
||||
echo ">>> 跳过 pglogical 初始化 (REPLICATION_MODE=$(REPLICATION_MODE))"; \
|
||||
fi
|
||||
|
||||
# =========================================
|
||||
@ -137,8 +149,8 @@ drop-db:
|
||||
@read -p "确定要删除数据库 $(DB_NAME)? [y/N] " confirm && \
|
||||
if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
|
||||
echo ">>> 强制断开现有连接 ..."; \
|
||||
if ! PGPASSWORD="$(DB_ADMIN_PASS)" psql -h $(DB_HOST) -U $(DB_ADMIN_USER) -d postgres \
|
||||
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='$(DB_NAME)' AND pid <> pg_backend_pid();"; then \
|
||||
if ! PGPASSWORD="$(DB_ADMIN_PASS)" psql -h $(DB_HOST) -U $(DB_ADMIN_USER) -d postgres \
|
||||
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='$(DB_NAME)' AND pid <> pg_backend_pid();"; then \
|
||||
echo "⚠️ 无法断开所有连接(需要超级用户权限)"; \
|
||||
fi; \
|
||||
echo ">>> 清理 pglogical schema ..."; \
|
||||
@ -146,10 +158,10 @@ drop-db:
|
||||
-c "DROP SCHEMA IF EXISTS pglogical CASCADE;" >/dev/null 2>&1 || \
|
||||
echo "⚠️ 无法删除 pglogical schema(数据库可能不存在或缺少权限)"; \
|
||||
echo ">>> 删除数据库 $(DB_NAME) ..."; \
|
||||
if PGPASSWORD="$(DB_ADMIN_PASS)" psql -h $(DB_HOST) -U $(DB_ADMIN_USER) -d postgres \
|
||||
-c "DROP DATABASE IF EXISTS $(DB_NAME);"; then \
|
||||
if PGPASSWORD="$(DB_ADMIN_PASS)" psql -h $(DB_HOST) -U $(DB_ADMIN_USER) -d postgres \
|
||||
-c "DROP DATABASE IF EXISTS $(DB_NAME);"; then \
|
||||
echo ">>> 数据库已删除"; \
|
||||
else \
|
||||
else \
|
||||
echo ">>> 删除失败"; \
|
||||
fi; \
|
||||
else \
|
||||
|
||||
36
account/sql/pgsync.users.example.yaml
Normal file
36
account/sql/pgsync.users.example.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
# Example pgsync job for unidirectional async sync of the users table.
|
||||
# Replace the DSN placeholders with concrete connection strings.
|
||||
# https://github.com/timeplus-io/pgsync
|
||||
|
||||
jobs:
|
||||
users_one_way:
|
||||
source:
|
||||
dsn: postgresql://replica:password@global-db:5432/account?sslmode=disable
|
||||
destination:
|
||||
dsn: postgresql://replica:password@cn-db:5432/account?sslmode=disable
|
||||
schema: public
|
||||
tables:
|
||||
users:
|
||||
delete: false
|
||||
columns:
|
||||
- uuid
|
||||
- username
|
||||
- password
|
||||
- email
|
||||
- role
|
||||
- level
|
||||
- groups
|
||||
- permissions
|
||||
- created_at
|
||||
- updated_at
|
||||
- version
|
||||
- origin_node
|
||||
- mfa_totp_secret
|
||||
- mfa_enabled
|
||||
- mfa_secret_issued_at
|
||||
- mfa_confirmed_at
|
||||
- email_verified_at
|
||||
- email_verified
|
||||
batch_size: 5000
|
||||
snapshot: true
|
||||
poll_interval: 30s
|
||||
@ -1,5 +1,13 @@
|
||||
# Account 数据库结构与双向同步指南
|
||||
|
||||
## 方案概览
|
||||
|
||||
| 目标 | 推荐方案 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| ✅ 一地写入 → 异步同步另一地 | [pgsync](#仅异步同步pgsync) | 使用逻辑导出/导入,无需超级权限,分钟级延迟 |
|
||||
| ✅ 双向写入 → 最终一致 | [pglogical](#双向写入最终一致pglogical) | 双主架构,需超级用户安装扩展 |
|
||||
| ✅ 结构迁移 / 数据导出备份 | `migratectl` + `pgsync` + `cron` | CLI 执行 schema,pgsync 定时同步 |
|
||||
|
||||
使用新的 `migratectl` CLI 可以在不同环境下快速执行迁移、校验和重置操作:
|
||||
|
||||
```bash
|
||||
@ -9,6 +17,63 @@ go run ./cmd/migratectl/main.go migrate --dsn "$DB_URL"
|
||||
# 对比 CN 与 Global 节点结构一致性
|
||||
go run ./cmd/migratectl/main.go check --cn "$CN_DSN" --global "$GLOBAL_DSN"
|
||||
|
||||
## 仅异步同步(pgsync)
|
||||
|
||||
当只需要将用户表从主区域异步同步到次区域时,推荐使用 [pgsync](https://github.com/timeplus-io/pgsync)。
|
||||
|
||||
1. 初始化基础 schema(默认 `REPLICATION_MODE=pgsync` 不会执行任何 pglogical 步骤):
|
||||
|
||||
```bash
|
||||
make -C account init-db REPLICATION_MODE=pgsync DB_URL="$SOURCE_DB_URL"
|
||||
make -C account init-db REPLICATION_MODE=pgsync DB_URL="$DEST_DB_URL"
|
||||
```
|
||||
|
||||
2. 编辑 `account/sql/pgsync.users.example.yaml`,替换源端与目标端 DSN。
|
||||
|
||||
3. 使用 pgsync 持续同步,可结合 cron 运行增量同步:
|
||||
|
||||
```bash
|
||||
# 全量初始化
|
||||
pgsync --config account/sql/pgsync.users.example.yaml --once
|
||||
|
||||
# 每分钟增量同步
|
||||
* * * * * /usr/local/bin/pgsync --config /path/to/pgsync.users.yaml >> /var/log/pgsync.log 2>&1
|
||||
```
|
||||
|
||||
4. 如需同步更多表,只需在配置文件中新增表条目。所有表均共享基础 schema,无需超级用户权限。
|
||||
|
||||
> 📌 `schema.sql` 中的 `origin_node` 默认值为 `local`。在 pgsync 场景下可以保持默认,或者在迁移脚本中通过 `ALTER TABLE ... ALTER COLUMN origin_node SET DEFAULT 'global'` 为不同区域设置标记。
|
||||
|
||||
## 双向写入最终一致(pglogical)
|
||||
|
||||
当需要两地同时写入并保持最终一致时,可切换到 pglogical 模式。
|
||||
|
||||
1. 确保数据库超级用户已经安装 `pglogical` 扩展。
|
||||
2. 初始化 schema 并应用 pglogical 默认值补丁:
|
||||
|
||||
```bash
|
||||
make -C account init-db REPLICATION_MODE=pglogical DB_URL="$REGION_DB_URL" \
|
||||
DB_ADMIN_USER=postgres DB_ADMIN_PASS=secret
|
||||
```
|
||||
|
||||
该命令依次执行:
|
||||
- `sql/schema.sql`:业务基础结构;
|
||||
- `sql/schema_pglogical_init.sql`:在 `pglogical` schema 中安装扩展;
|
||||
- `sql/schema_pglogical_patch.sql`:将 `origin_node` 默认值绑定到 `pglogical.node_name`。
|
||||
|
||||
3. 参考下方模板配置双节点复制:
|
||||
|
||||
```bash
|
||||
make -C account init-pglogical-region \
|
||||
REGION_DB_URL="$REGION_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=xxxx"
|
||||
```
|
||||
|
||||
4. 在另一侧节点重复执行并互为订阅,实现双主写入。
|
||||
|
||||
## 🔐 权限与 Schema 设置
|
||||
|
||||
- 1️⃣ 授权 pglogical schema 使用权限
|
||||
@ -41,10 +106,12 @@ GRANT USAGE ON SCHEMA pglogical TO shenlan;
|
||||
|
||||
| 步骤 | 节点 | 脚本 / 命令 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| 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 |
|
||||
| 1️⃣ | Global | schema.sql | 创建业务结构(含 version/origin_node) |
|
||||
| 2️⃣ | CN | schema.sql | 创建相同业务结构 |
|
||||
| 3️⃣ | Global | schema_pglogical_patch.sql | 绑定 origin_node 默认值到 pglogical |
|
||||
| 4️⃣ | CN | schema_pglogical_patch.sql | 同上 |
|
||||
| 5️⃣ | Global | schema_pglogical_region.sql + 参数 | 定义 Global provider + 订阅 CN |
|
||||
| 6️⃣ | CN | schema_pglogical_region.sql + 参数 | 定义 CN provider + 订阅 Global |
|
||||
|
||||
💡 执行 `schema_pglogical_region.sql` 或对应的 `make init-pglogical-region-*` 目标前,请确保连接用户拥有 PostgreSQL 超级用户权限。
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
-- =========================================
|
||||
-- schema_base_bidirectional_enhanced.sql
|
||||
-- Shared schema for pglogical bidirectional sync (Multi-Master Safe)
|
||||
-- schema.sql
|
||||
-- Base business schema for the account service.
|
||||
-- Works with both one-way async sync (pgsync) and pglogical multi-master.
|
||||
-- PostgreSQL 16 + gen_random_uuid()
|
||||
-- =========================================
|
||||
|
||||
@ -19,8 +19,7 @@ DROP TABLE IF EXISTS public.admin_settings CASCADE;
|
||||
-- =========================================
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
|
||||
|
||||
-- pglogical schema is initialized separately to avoid interfering with
|
||||
-- business schema snapshots. See schema_pglogical_init.sql.
|
||||
-- pglogical specific defaults are now applied by schema_pglogical_patch.sql.
|
||||
|
||||
-- =========================================
|
||||
-- Functions
|
||||
@ -55,7 +54,6 @@ BEGIN
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- =========================================
|
||||
-- Tables
|
||||
-- =========================================
|
||||
|
||||
@ -71,7 +69,7 @@ CREATE TABLE public.users (
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
version BIGINT NOT NULL DEFAULT 0, -- 🔢 行版本号
|
||||
origin_node TEXT DEFAULT current_setting('pglogical.node_name', true), -- 🌍 来源节点
|
||||
origin_node TEXT NOT NULL DEFAULT 'local', -- 🌍 来源节点,可在不同区域通过 ALTER TABLE 或 pglogical patch 覆盖
|
||||
mfa_totp_secret TEXT,
|
||||
mfa_enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
mfa_secret_issued_at TIMESTAMPTZ,
|
||||
@ -88,7 +86,7 @@ CREATE TABLE public.identities (
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
version BIGINT NOT NULL DEFAULT 0,
|
||||
origin_node TEXT DEFAULT current_setting('pglogical.node_name', true),
|
||||
origin_node TEXT NOT NULL DEFAULT 'local',
|
||||
CONSTRAINT identities_provider_external_id_uk UNIQUE (provider, external_id)
|
||||
);
|
||||
|
||||
@ -100,7 +98,7 @@ CREATE TABLE public.sessions (
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
version BIGINT NOT NULL DEFAULT 0,
|
||||
origin_node TEXT DEFAULT current_setting('pglogical.node_name', true)
|
||||
origin_node TEXT NOT NULL DEFAULT 'local'
|
||||
);
|
||||
|
||||
CREATE TABLE public.admin_settings (
|
||||
@ -109,7 +107,7 @@ CREATE TABLE public.admin_settings (
|
||||
role TEXT NOT NULL,
|
||||
enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
version BIGINT NOT NULL DEFAULT 1,
|
||||
origin_node TEXT DEFAULT current_setting('pglogical.node_name', true),
|
||||
origin_node TEXT NOT NULL DEFAULT 'local',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT admin_settings_module_role_uk UNIQUE (module_key, role)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
-- =========================================
|
||||
-- schema_pglogical_init.sql
|
||||
-- Dedicated pglogical schema initialization separated from business schema
|
||||
-- =========================================
|
||||
-- Dedicated pglogical schema initialization separated from business schema.
|
||||
-- Execute schema_pglogical_patch.sql afterwards to align origin defaults.
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
|
||||
18
account/sql/schema_pglogical_patch.sql
Normal file
18
account/sql/schema_pglogical_patch.sql
Normal file
@ -0,0 +1,18 @@
|
||||
-- =========================================
|
||||
-- schema_pglogical_patch.sql
|
||||
-- Apply pglogical-aware defaults on top of the base business schema.
|
||||
-- Run only when the cluster enables pglogical for bidirectional writes.
|
||||
-- =========================================
|
||||
|
||||
-- Align origin_node defaults with pglogical node name when available.
|
||||
ALTER TABLE public.users
|
||||
ALTER COLUMN origin_node SET DEFAULT COALESCE(current_setting('pglogical.node_name', true), 'local');
|
||||
|
||||
ALTER TABLE public.identities
|
||||
ALTER COLUMN origin_node SET DEFAULT COALESCE(current_setting('pglogical.node_name', true), 'local');
|
||||
|
||||
ALTER TABLE public.sessions
|
||||
ALTER COLUMN origin_node SET DEFAULT COALESCE(current_setting('pglogical.node_name', true), 'local');
|
||||
|
||||
ALTER TABLE public.admin_settings
|
||||
ALTER COLUMN origin_node SET DEFAULT COALESCE(current_setting('pglogical.node_name', true), 'local');
|
||||
@ -2,8 +2,7 @@
|
||||
-- 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=...
|
||||
-- 提供所需参数。
|
||||
-- 在运行本脚本前,请确保已执行 schema.sql 与 schema_pglogical_patch.sql。
|
||||
-- =========================================
|
||||
|
||||
\if :{?NODE_NAME}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user