accounts/sql
2026-02-05 11:27:11 +08:00
..
20251001.migrate.to_uuid.md chore: move account sql assets and update migrate docs 2026-01-16 11:47:10 +08:00
20260204_rbac_root_constraints.sql feat: enforce root account and introduce RBAC policy scaffolding 2026-02-04 13:36:24 +08:00
20260205_agents_table.sql feat(agent-persistence): implement PostgreSQL persistence for agent registry 2026-02-05 08:34:25 +08:00
20260205_sessions_token_unique.sql fix: correct sessions table column names from user_id to user_uuid 2026-02-05 11:00:57 +08:00
embed.go chore: move account sql assets and update migrate docs 2026-01-16 11:47:10 +08:00
pgsync.users.example.yaml chore: move account sql assets and update migrate docs 2026-01-16 11:47:10 +08:00
readme.md feat: enforce root account and introduce RBAC policy scaffolding 2026-02-04 13:36:24 +08:00
reset_public_schema.sql chore: move account sql assets and update migrate docs 2026-01-16 11:47:10 +08:00
schema_pglogical_init.sql chore: move account sql assets and update migrate docs 2026-01-16 11:47:10 +08:00
schema_pglogical_patch.sql chore: move account sql assets and update migrate docs 2026-01-16 11:47:10 +08:00
schema_pglogical_region.sql chore: move account sql assets and update migrate docs 2026-01-16 11:47:10 +08:00
schema.sql feat: Implement persistent session management by moving session logic to the store interface and adding a sessions table. 2026-02-05 11:27:11 +08:00

Account 数据库结构与双向同步指南

RBAC / Root 迁移

  • 新增迁移脚本:sql/20260204_rbac_root_constraints.sql
  • 目的:
    • 创建 RBAC 元数据表(rbac_roles / rbac_permissions / rbac_role_permissions
    • 增加 root 唯一约束(仅允许一个 role=root
    • 增加 root 邮箱约束(role=root 必须是 admin@svc.plus

执行示例:

psql "$DB_URL" -v ON_ERROR_STOP=1 -f sql/20260204_rbac_root_constraints.sql

方案概览

目标 推荐方案 说明
一地写入 → 异步同步另一地 pgsync 使用逻辑导出/导入,无需超级权限,分钟级延迟
双向写入 → 最终一致 pglogical 双主架构,需超级用户安装扩展
结构迁移 / 数据导出备份 migratectl + pgsync + cron CLI 执行 schemapgsync 定时同步

使用新的 migratectl CLI 可以在不同环境下快速执行迁移、校验和重置操作:

# 初始化或升级 schema
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"
  1. 编辑 account/sql/pgsync.users.example.yaml,替换源端与目标端 DSN。

  2. 使用 pgsync 持续同步,可结合 cron 运行增量同步:

    # 全量初始化
    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
    
  3. 如需同步更多表,只需在配置文件中新增表条目。所有表均共享基础 schema无需超级用户权限。

📌 schema.sql 中的 origin_node 默认值为 local。在 pgsync 场景下可以保持默认,或者在迁移脚本中通过 ALTER TABLE ... ALTER COLUMN origin_node SET DEFAULT 'global' 为不同区域设置标记。

双向写入最终一致pglogical

当需要两地同时写入并保持最终一致时,可切换到 pglogical 模式。

  1. 确保数据库超级用户已经安装 pglogical 扩展。

  2. 初始化 schema 并应用 pglogical 默认值补丁:

    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. 参考下方模板配置双节点复制:

    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_USER} password=${PGLOGICAL_PASSWORD}" \
      SUBSCRIPTION_NAME=sub_from_global \
      PROVIDER_DSN="host=global-homepage.svc.plus port=5432 dbname=account user=${PGLOGICAL_USER} password=${PGLOGICAL_PASSWORD}"
    
  4. 在另一侧节点重复执行并互为订阅,实现双主写入。

🔐 权限与 Schema 设置

  • 1 授权 pglogical schema 使用权限

pglogical schema 与业务 schema 分离,以防逻辑复制函数污染业务层。

在初始化完成后执行:

bash 复制代码 sudo -u postgres psql -d account -c "GRANT USAGE ON SCHEMA pglogical TO PUBLIC;" 2 授权业务用户app_user sql 复制代码 -- 登录 postgres sudo -u postgres psql -d account

-- 授权 app_user 对 public schema 全权限 ALTER SCHEMA public OWNER TO app_user; GRANT ALL ON SCHEMA public TO app_user; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO app_user; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO app_user; GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO app_user;

-- 授权 pglogical schema 使用权限(仅使用,不可修改) GRANT USAGE ON SCHEMA pglogical TO app_user;

\q ⚙️ 执行顺序建议

步骤 节点 脚本 / 命令 说明
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 超级用户权限。

手动执行模版脚本

使用相同的 schema_pglogical_region.sql 模版即可初始化 Global 与 CN 两个节点,只需传入不同的变量:

# 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_USER} password=${PGLOGICAL_PASSWORD}' \
  -v SUBSCRIPTION_NAME=sub_from_cn \
  -v PROVIDER_DSN='host=cn-homepage.svc.plus port=5432 dbname=account user=${PGLOGICAL_USER} password=${PGLOGICAL_PASSWORD}' \
  -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_USER} password=${PGLOGICAL_PASSWORD}' \
  -v SUBSCRIPTION_NAME=sub_from_global \
  -v PROVIDER_DSN='host=global-homepage.svc.plus port=5432 dbname=account user=${PGLOGICAL_USER} password=${PGLOGICAL_PASSWORD}' \
  -f account/sql/schema_pglogical_region.sql

也可以通过新的 make init-pglogical-region 目标自定义变量,例如:

make init-pglogical-region \
  REGION_DB_URL="$REGION_DB_URL" \
  NODE_NAME=node_example \
  NODE_DSN="host=example port=5432 dbname=account user=${PGLOGICAL_USER} password=${PGLOGICAL_PASSWORD}" \
  SUBSCRIPTION_NAME=sub_from_peer \
  PROVIDER_DSN="host=peer port=5432 dbname=account user=${PGLOGICAL_USER} password=${PGLOGICAL_PASSWORD}"
  • 若使用业务账号(如 app_user执行初始化PostgreSQL 会提示缺少超级用户权限并跳过 pglogical 初始化。
  • 建议改用 postgres 等超级用户连接执行,或由管理员预先安装 pglogical 扩展并授予业务用户访问权限。
  • 如果扩展已由管理员创建,可直接重新运行 make init-pglogical-region-cn 完成复制集与订阅配置。

🧩 验证同步状态 sql 复制代码 SELECT * FROM pglogical.show_subscription_status(); 若输出中:

ini 复制代码 status = 'replicating' 即表示双向复制同步正常。

🚀 双向同步特性汇总 特性 实现机制 双主写入 两端都是 Provider + Subscriber 唯一性保障 所有主键为 gen_random_uuid(),避免冲突 邮箱唯一 lower(email) 唯一索引 异步复制 WAL 级逻辑同步,自动断点续传 结构一致性 schema_base_bidirectional_enhanced.sql 保证完全相同 幂等可重建 全部 IF NOT EXISTS可重复执行 扩展性 可新增字段或表,通过 replication_set_add_table() 同步 冲突检测 version + origin_node 字段支持双写检测与合并

🔍 冲突检测与合并策略 双向同步可能出现两节点同时更新同一行的情况。 可通过 version 与 updated_at 字段进行检测:

sql 复制代码 -- 检查 CN 与 Global 版本不一致的行 SELECT uuid, username, version, updated_at, origin_node FROM users WHERE version <> ( SELECT version FROM dblink('dbname=account_global', 'SELECT version, uuid FROM users') AS global_users(uuid uuid, version bigint) WHERE global_users.uuid = users.uuid );

-- 可根据 version 或 updated_at 决定“最后写赢” 推荐策略:

比较 version → 较高者为最终版本;

若版本相同,则以 updated_at 较新的为准;

origin_node 可用于回溯更新来源CN / Global

🧱 Schema 增强说明(相较旧版) 字段 类型 说明 version BIGINT DEFAULT 0 行级版本号,防止冲突或支持 last-write-wins origin_node TEXT DEFAULT current_setting('pglogical.node_name', true) 标识该记录来源节点 bump_version() trigger function 每次更新自动自增 version *_bump_version trigger 自动维护版本号

🧠 附录:生产建议 定期执行结构一致性校验migratectl check

对业务字段更新保持幂等逻辑(可多次执行)

为新表自动加入:

uuid 主键gen_random_uuid()

created_at, updated_at, version, origin_node

保持两端 PostgreSQL 参数一致:

conf 复制代码 wal_level = logical max_replication_slots = 10 max_wal_senders = 10 shared_preload_libraries = 'pglogical' 🧩 验证 checklist

Global 与 CN 节点均执行相同的 schema

所有表包含 version + origin_node

pglogical 双向订阅已建立

status = 'replicating'

version 字段随 UPDATE 自增