18 KiB
生产环境数据库同步指南
生成时间: 2026-03-12
测试环境数据库: taiji
生产环境数据库: taiji_prod
📋 迁移总览
测试环境在 2026年3月9-12日期间执行了 8个数据库迁移,需要同步到生产环境:
| 迁移编号 | 文件名 | 类型 | 影响范围 | 风险等级 |
|---|---|---|---|---|
| 017 | add_external_data_tools.sql | 新增表 | 外部数据工具功能 | 🟢 低 |
| 018 | add_external_toolkits.sql | 新增表 | 外部工具集功能 | 🟢 低 |
| 019 | add_agent_model_name.sql | 字段新增 | agents、agent_billing_records |
🟢 低 |
| 020 | fix_quota_defaults.sql | 字段修改 | 配额表(3个表) | 🟡 中 |
| 021 | fix_agent_type_length.sql | 字段修改 | agent_billing_records |
🟢 低 |
| 022 | fix_eu_equals_cost.sql | 字段类型+数据修正 | agent_billing_records、model_billing_records |
🟡 中 |
| 023 | fix_billing_channel_id.sql | 数据修正 | agent_billing_records、model_billing_records |
🟢 低 |
| 024 | add_record_type.sql | 字段新增+数据修正 | agent_billing_records |
🟡 中 |
📝 详细迁移内容
迁移 017: 添加外部数据工具表
文件: services/mcp-server/migrations/017_add_external_data_tools.sql
目的: 支持用户创建和管理自定义的外部API数据工具
操作:
-- 创建表
CREATE TABLE external_data_tools (
id UUID PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
url VARCHAR(500) NOT NULL,
method VARCHAR(10) DEFAULT 'POST',
auth_type VARCHAR(20) DEFAULT 'none',
tool_ref_id VARCHAR(100) UNIQUE,
status VARCHAR(20) DEFAULT 'pending',
error_message TEXT,
owner_id UUID NOT NULL REFERENCES users(id),
tenant_id UUID REFERENCES users(id),
channel_id UUID REFERENCES channels(id),
is_active BOOLEAN DEFAULT TRUE,
usage_count INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 索引
CREATE INDEX idx_external_data_tool_owner ON external_data_tools(owner_id);
CREATE INDEX idx_external_data_tool_ref ON external_data_tools(tool_ref_id);
CREATE INDEX idx_external_data_tool_status ON external_data_tools(status);
影响: 新功能,对现有数据无影响
回滚方案:
DROP TABLE IF EXISTS external_data_tools CASCADE;
迁移 018: 添加外部数据工具集表
文件: services/mcp-server/migrations/018_add_external_toolkits.sql
目的: 允许用户将多个外部工具组合成工具集,方便部署自定义Agent
操作:
-- 创建表
CREATE TABLE external_toolkits (
id UUID PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
tool_ids JSONB NOT NULL DEFAULT '[]'::jsonb,
owner_id UUID NOT NULL REFERENCES users(id),
tenant_id UUID REFERENCES users(id),
channel_id UUID REFERENCES channels(id),
is_active BOOLEAN DEFAULT TRUE,
usage_count INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_toolkit_name_owner UNIQUE (name, owner_id)
);
-- 索引
CREATE INDEX idx_external_toolkit_owner ON external_toolkits(owner_id);
影响: 新功能,对现有数据无影响
回滚方案:
DROP TABLE IF EXISTS external_toolkits CASCADE;
迁移 019: 为Agent添加模型名称字段 ⭐ 重要
文件: services/mcp-server/migrations/019_add_agent_model_name.sql
目的:
- 记录每个Agent使用的LLM模型(如
azure/gpt-4,gemini/gemini-pro) - 支持计费记录中追踪模型使用情况
操作:
-- 1. agents 表添加 model_name 字段
ALTER TABLE agents ADD COLUMN IF NOT EXISTS model_name VARCHAR(100);
CREATE INDEX IF NOT EXISTS idx_agent_model_name ON agents(model_name);
-- 2. agent_billing_records 表添加 model_name 字段
ALTER TABLE agent_billing_records ADD COLUMN IF NOT EXISTS model_name VARCHAR(100);
CREATE INDEX IF NOT EXISTS idx_agent_billing_model_name ON agent_billing_records(model_name);
影响:
- 现有Agent的
model_name为 NULL(可接受) - 新创建的Agent将记录模型信息
注意事项:
- ⚠️ 如果生产环境已有该字段,使用
IF NOT EXISTS将安全跳过 - 该字段为 可空,不强制现有记录填充
回滚方案:
ALTER TABLE agents DROP COLUMN IF EXISTS model_name;
ALTER TABLE agent_billing_records DROP COLUMN IF EXISTS model_name;
DROP INDEX IF EXISTS idx_agent_model_name;
DROP INDEX IF EXISTS idx_agent_billing_model_name;
迁移 020: 修复配额字段默认值 ⚠️ 需要谨慎
文件: services/mcp-server/migrations/020_fix_quota_defaults.sql
目的:
- 消除配额字段的 NULL 值,避免计算错误
- 添加 NOT NULL 约束和检查约束
操作:
-- 1. tenant_custom_agent_quotas 表
ALTER TABLE tenant_custom_agent_quotas
ALTER COLUMN cpu_quota SET DEFAULT 0, ALTER COLUMN cpu_quota SET NOT NULL,
ALTER COLUMN memory_quota SET DEFAULT 0, ALTER COLUMN memory_quota SET NOT NULL,
ALTER COLUMN cpu_used SET DEFAULT 0, ALTER COLUMN cpu_used SET NOT NULL,
ALTER COLUMN memory_used SET DEFAULT 0, ALTER COLUMN memory_used SET NOT NULL,
ALTER COLUMN agent_count SET DEFAULT 0, ALTER COLUMN agent_count SET NOT NULL;
UPDATE tenant_custom_agent_quotas SET cpu_quota = 0 WHERE cpu_quota IS NULL;
UPDATE tenant_custom_agent_quotas SET memory_quota = 0 WHERE memory_quota IS NULL;
UPDATE tenant_custom_agent_quotas SET cpu_used = 0 WHERE cpu_used IS NULL;
UPDATE tenant_custom_agent_quotas SET memory_used = 0 WHERE memory_used IS NULL;
UPDATE tenant_custom_agent_quotas SET agent_count = 0 WHERE agent_count IS NULL;
-- 添加检查约束
ALTER TABLE tenant_custom_agent_quotas
ADD CONSTRAINT chk_tenant_cpu_used CHECK (cpu_used <= cpu_quota),
ADD CONSTRAINT chk_tenant_memory_used CHECK (memory_used <= memory_quota);
-- 2. channel_custom_agent_quotas 表(类似操作)
-- 3. platform_agent_quotas 表(类似操作)
影响:
- ⚠️ 数据修改: 将所有 NULL 值更新为 0
- ⚠️ 约束添加: 新增检查约束,使用量不能超过配额
⚠️ 执行前检查:
-- 检查生产环境是否有 NULL 值
SELECT
COUNT(*) as total_records,
COUNT(CASE WHEN cpu_quota IS NULL THEN 1 END) as null_cpu_quota,
COUNT(CASE WHEN memory_quota IS NULL THEN 1 END) as null_memory_quota
FROM tenant_custom_agent_quotas;
-- 检查是否有使用量超过配额的记录(会导致约束添加失败)
SELECT * FROM tenant_custom_agent_quotas WHERE cpu_used > cpu_quota;
SELECT * FROM tenant_custom_agent_quotas WHERE memory_used > memory_quota;
回滚方案:
-- 删除约束(如果需要)
ALTER TABLE tenant_custom_agent_quotas DROP CONSTRAINT IF EXISTS chk_tenant_cpu_used;
ALTER TABLE tenant_custom_agent_quotas DROP CONSTRAINT IF EXISTS chk_tenant_memory_used;
-- 注意:无法回滚 NOT NULL 约束和数据修改
迁移 021: 修复agent_type字段长度
文件: services/mcp-server/migrations/021_fix_agent_type_length.sql
目的: 修复 microsoft_learn_agent(22字符)超出 VARCHAR(20) 限制的问题
操作:
ALTER TABLE agent_billing_records ALTER COLUMN agent_type TYPE VARCHAR(100);
影响: 扩展字段长度,对现有数据无负面影响
测试建议:
-- 检查是否有被截断的数据
SELECT agent_type, LENGTH(agent_type) as len, COUNT(*)
FROM agent_billing_records
GROUP BY agent_type
ORDER BY len DESC;
回滚方案:
-- 仅在确认所有值都 ≤20 字符时才能回滚
ALTER TABLE agent_billing_records ALTER COLUMN agent_type TYPE VARCHAR(20);
迁移 022: 修复EU计算逻辑 ⚠️ 重要数据修正
文件: services/mcp-server/migrations/022_fix_eu_equals_cost.sql
目的:
- 旧逻辑:
EU = ceil(duration_seconds / 10),即 1 EU = 10秒 - 新逻辑:
EU = Cost(美元),即 1 EU = 1 美元 - 将历史数据的
eu_consumed修正为cost的值
操作:
-- 1. 修改字段类型以支持小数
ALTER TABLE agent_billing_records
ALTER COLUMN eu_consumed TYPE NUMERIC(12, 4);
-- 2. 更新历史数据
UPDATE agent_billing_records
SET eu_consumed = cost
WHERE cost IS NOT NULL AND cost > 0;
UPDATE model_billing_records
SET eu_consumed = total_cost
WHERE total_cost IS NOT NULL AND total_cost > 0;
-- 3. 添加注释说明
COMMENT ON COLUMN agent_billing_records.eu_consumed IS 'EU 消耗(1 EU = 1 美元,EU = Cost)';
影响:
- ⚠️ 修改所有历史计费记录的
eu_consumed值 - 会导致历史数据的EU统计发生变化
⚠️ 执行前备份:
-- 备份历史数据
CREATE TABLE agent_billing_records_backup_20260312 AS
SELECT * FROM agent_billing_records;
CREATE TABLE model_billing_records_backup_20260312 AS
SELECT * FROM model_billing_records;
验证:
-- 检查 EU 和 Cost 是否一致
SELECT id, agent_name, eu_consumed, cost,
CASE WHEN ABS(eu_consumed - cost) < 0.0001 THEN 'OK' ELSE 'MISMATCH' END as status
FROM agent_billing_records
WHERE cost > 0
LIMIT 20;
回滚方案:
-- 从备份表恢复
UPDATE agent_billing_records abr
SET eu_consumed = backup.eu_consumed
FROM agent_billing_records_backup_20260312 backup
WHERE abr.id = backup.id;
迁移 023: 修复计费记录中缺失的channel_id
文件: services/mcp-server/migrations/023_fix_billing_channel_id.sql
目的: 修复计费记录创建时未正确设置 channel_id 的问题
操作:
-- 1. 更新 AgentBillingRecord
UPDATE agent_billing_records abr
SET channel_id = u.channel_id
FROM users u
WHERE abr.user_id = u.id
AND abr.channel_id IS NULL
AND u.channel_id IS NOT NULL;
-- 2. 更新 ModelBillingRecord
UPDATE model_billing_records mbr
SET channel_id = u.channel_id
FROM users u
WHERE mbr.tenant_id = u.id
AND mbr.channel_id IS NULL
AND u.channel_id IS NOT NULL;
影响: 补充缺失的 channel_id,提高数据完整性
执行前检查:
-- 检查有多少记录缺失 channel_id
SELECT
'AgentBillingRecord' as table_name,
COUNT(*) as total,
COUNT(channel_id) as with_channel,
COUNT(*) - COUNT(channel_id) as without_channel
FROM agent_billing_records
UNION ALL
SELECT
'ModelBillingRecord' as table_name,
COUNT(*) as total,
COUNT(channel_id) as with_channel,
COUNT(*) - COUNT(channel_id) as without_channel
FROM model_billing_records;
回滚方案: 无需回滚(数据修正)
迁移 024: 添加record_type字段 ⚠️ 重要业务逻辑变更
文件: services/mcp-server/migrations/024_add_record_type.sql
目的: 区分两种计费方式
vm_runtime: VM运行时间计费(一个Agent = 一条记录,周期性更新)api_call: API调用计费(每次调用 = 一条新记录)
操作:
-- 1. 添加字段
ALTER TABLE agent_billing_records
ADD COLUMN IF NOT EXISTS record_type VARCHAR(20) NOT NULL DEFAULT 'vm_runtime';
-- 2. 推断现有记录类型
UPDATE agent_billing_records
SET record_type = 'api_call'
WHERE request_id IS NOT NULL
AND request_id != ''
AND end_time IS NOT NULL;
-- 3. 添加索引
CREATE INDEX IF NOT EXISTS idx_agent_billing_record_type
ON agent_billing_records(record_type);
CREATE INDEX IF NOT EXISTS idx_agent_billing_vm_runtime
ON agent_billing_records(record_type, end_time)
WHERE record_type = 'vm_runtime' AND end_time IS NULL;
影响:
- 新增业务逻辑字段
- 周期计费任务将只更新
record_type='vm_runtime'的记录
验证:
SELECT
record_type,
COUNT(*) as count,
COUNT(CASE WHEN end_time IS NULL THEN 1 END) as running_count,
COUNT(CASE WHEN end_time IS NOT NULL THEN 1 END) as completed_count
FROM agent_billing_records
GROUP BY record_type;
回滚方案:
ALTER TABLE agent_billing_records DROP COLUMN IF EXISTS record_type;
DROP INDEX IF EXISTS idx_agent_billing_record_type;
DROP INDEX IF EXISTS idx_agent_billing_vm_runtime;
🚀 执行顺序和依赖关系
迁移必须按以下顺序执行(有依赖关系):
017 ─────┐
├──→ 可并行执行
018 ─────┘
019 ─→ 020 ─→ 021 ─→ 022 ─→ 023 ─→ 024
↑ ↑
│ │
修改配额约束 修改计费逻辑(最关键)
推荐分批执行:
- 第一批(新功能): 017, 018, 019
- 第二批(数据修正): 020, 021, 023
- 第三批(核心逻辑): 022, 024
⚙️ 执行步骤
步骤 1: 备份生产数据库 🔴 必做
# 使用项目提供的备份脚本
cd /home/taiji/tools/taiji-AI-PAD
bash scripts/backup_postgres.sh
# 或手动备份
kubectl exec -n taiji-ai-pad <postgres-pod> -- \
pg_dump -U postgres taiji_prod > backup_taiji_prod_$(date +%Y%m%d_%H%M%S).dump
步骤 2: 检查生产环境状态
# 检查数据库连接
python services/mcp-server/check_prod_database_status.py
# 检查计费健康状态
python services/mcp-server/check_billing_health.py
# 检查配额数据
python services/mcp-server/check_quota_data.py
步骤 3: 使用自动同步脚本(推荐)
# 使用交互式同步脚本
bash scripts/sync_prod_database.sh
该脚本会:
- ✅ 自动检查K8s集群连接
- ✅ 显示当前数据库配置
- ✅ 逐个执行迁移,每步都需要确认
- ✅ 记录执行日志
步骤 4: 手动执行(如果需要更细粒度控制)
# 进入mcp-server Pod
POD_NAME=$(kubectl get pods -n taiji-ai-pad -l app=mcp-server -o jsonpath='{.items[0].metadata.name}')
kubectl exec -it -n taiji-ai-pad $POD_NAME -- bash
# 切换到migrations目录
cd /app/migrations
# 执行迁移(按顺序)
python run_017_migration.py
python run_018_migration.py
python run_019_migration.py
python run_020_fix_quota_defaults.py
# ... 依次执行
步骤 5: 验证结果
# 检查表结构
kubectl exec -n taiji-ai-pad <postgres-pod> -- \
psql -U postgres -d taiji_prod -c "\d agent_billing_records"
# 检查数据完整性
kubectl exec -n taiji-ai-pad <postgres-pod> -- \
psql -U postgres -d taiji_prod -c "
SELECT
COUNT(*) as total_records,
COUNT(record_type) as with_record_type,
COUNT(model_name) as with_model_name,
COUNT(channel_id) as with_channel_id
FROM agent_billing_records;
"
# 检查EU和Cost的一致性
kubectl exec -n taiji-ai-pad <postgres-pod> -- \
psql -U postgres -d taiji_prod -c "
SELECT
COUNT(*) as total,
COUNT(CASE WHEN ABS(eu_consumed - cost) < 0.0001 THEN 1 END) as consistent,
COUNT(CASE WHEN ABS(eu_consumed - cost) >= 0.0001 THEN 1 END) as inconsistent
FROM agent_billing_records
WHERE cost > 0;
"
步骤 6: 重启服务(如果需要)
# 重启mcp-server以应用新配置
kubectl rollout restart deployment/mcp-server -n taiji-ai-pad
# 等待Pod就绪
kubectl rollout status deployment/mcp-server -n taiji-ai-pad
⚠️ 风险和注意事项
🔴 高风险操作
-
迁移 022(EU计算逻辑修改)
- 会修改所有历史计费记录
- 建议在业务低峰期执行
- 必须先备份
-
迁移 020(配额约束)
- 会添加检查约束
- 如果有数据不一致(使用量>配额),迁移会失败
- 需要先修复数据
🟡 中风险操作
- 迁移 024(record_type字段)
- 修改了计费逻辑
- 需要确保周期计费任务已更新代码
🟢 低风险操作
- 迁移 017, 018, 019, 021, 023
- 这些主要是新增字段/表,对现有业务无影响
🔄 回滚计划
如果迁移后发现问题,按以下步骤回滚:
快速回滚(恢复备份)
# 停止服务
kubectl scale deployment/mcp-server -n taiji-ai-pad --replicas=0
# 恢复数据库
kubectl exec -n taiji-ai-pad <postgres-pod> -- \
psql -U postgres -c "DROP DATABASE taiji_prod;"
kubectl exec -n taiji-ai-pad <postgres-pod> -- \
psql -U postgres -c "CREATE DATABASE taiji_prod;"
kubectl exec -i -n taiji-ai-pad <postgres-pod> -- \
psql -U postgres taiji_prod < backup_taiji_prod_20260312_HHMMSS.dump
# 重启服务
kubectl scale deployment/mcp-server -n taiji-ai-pad --replicas=1
部分回滚(单个迁移)
参考每个迁移的"回滚方案"章节,执行对应的 SQL 语句。
📊 预期影响评估
数据量影响
-- 评估受影响的记录数
SELECT
'agent_billing_records' as table_name,
COUNT(*) as total_records,
pg_size_pretty(pg_total_relation_size('agent_billing_records')) as table_size
FROM agent_billing_records
UNION ALL
SELECT
'model_billing_records' as table_name,
COUNT(*) as total_records,
pg_size_pretty(pg_total_relation_size('model_billing_records')) as table_size
FROM model_billing_records;
停机时间估算
- 新表创建(017, 018): < 1秒
- 字段添加(019, 021, 024): 1-5秒(取决于表大小)
- 数据修正(020, 022, 023): 1-10分钟(取决于记录数)
建议总停机时间: 15-30分钟(保守估计)
✅ 完成检查清单
执行完成后,请确认:
- 所有迁移脚本执行成功,无错误
- 新表已创建:
external_data_tools,external_toolkits - 新字段已添加:
model_name,record_type eu_consumed和cost数据一致channel_id缺失值已修复- 配额约束已添加且无冲突
- 服务正常启动,无报错
- 健康检查通过
- 计费任务正常运行
- 备份文件已妥善保存
📞 问题反馈
如果迁移过程中遇到问题,请检查:
-
Pod日志:
kubectl logs -n taiji-ai-pad deployment/mcp-server --tail=100 -
数据库日志:
kubectl logs -n taiji-ai-pad <postgres-pod> --tail=100 -
运行健康检查:
python services/mcp-server/check_billing_health.py
📚 相关文档
📅 变更记录
| 日期 | 操作人 | 环境 | 迁移 | 结果 | 备注 |
|---|---|---|---|---|---|
| 2026-03-09~12 | - | 测试环境 | 017-024 | ✅ 成功 | 初次执行 |
| 待填写 | 待填写 | 生产环境 | 017-024 | 待填写 | 待填写 |
最后更新: 2026-03-12
文档版本: v1.0