# PostgreSQL 15 数据库部署策略 - 摸底报告 > **文档版本:** v1.1 > **创建日期:** 2025-12-14 > **最后更新:** 2025-12-14 > **数据库版本:** PostgreSQL 15.14 (Docker: postgres:15-alpine) > **目标环境:** 阿里云 RDS PostgreSQL 15 > **报告类型:** 技术摸底 + 部署策略 --- ## 📝 版本修订记录 ### v1.1 (2025-12-14) - 专业建议修正版 **修正要点:** 1. **✅ pg-boss表"自愈"机制澄清** - 修正:pg-boss会在应用启动时自动创建表,只要权限够 - 降低严重性:从"严重差异❌"改为"自愈机制✅(无需担心)" - 增加说明:pg-boss的智能自愈能力和权限要求 2. **✅ 白名单配置强化说明** - 新增:必须使用VPC网段,不能用单机IP - 示例:172.16.0.0/12(SAE的VPC网段) - 原因:SAE实例IP会变化,单机IP会导致连接失败 - 位置:RDS准备、网络安全、下一步行动多处强调 3. **✅ 备份策略简化(更务实)** - 修正:初期只需RDS自动备份(含日志备份) - 降级:pg_dump ECS脚本从"第三道防线"降为"可选(业务做大后再考虑)" - 理由:RDS自动备份+PITR已足够,省心省力 - 保留:脚本示例作为参考,标注"⚠️ 仅供参考,初期不需要" **贡献者:** 项目技术团队反馈 --- --- ## 📋 目录 1. [本地数据库真实情况](#本地数据库真实情况) 2. [Prisma与数据库的差异分析](#prisma与数据库的差异分析) 3. [代码如何连接数据库](#代码如何连接数据库) 4. [首次部署方案](#首次部署方案) 5. [未来更新策略](#未来更新策略) 6. [RDS备份策略](#rds备份策略) 7. [最佳实践与禁止操作](#最佳实践与禁止操作) --- ## 本地数据库真实情况 ### 1. 基础信息 ```bash Docker镜像:postgres:15-alpine 数据库名称:ai_clinical_research 连接信息:postgresql://postgres:postgres@localhost:5432/ai_clinical_research 数据库大小:26 MB(测试/开发环境) 用户数据:3个用户账号 ``` ### 2. Schema隔离架构(10个Schema)✅ 你的数据库已经成功实施了10个Schema隔离架构: | # | Schema名称 | 表数量 | 状态 | 说明 | |---|-----------|--------|------|------| | 1 | `platform_schema` | 8个表 | ✅ 已实施 | 平台基础设施(用户、pg-boss队列) | | 2 | `aia_schema` | 5个表 | ✅ 已实施 | AI智能问答(项目、对话、消息) | | 3 | `pkb_schema` | 5个表 | ✅ 已实施 | 个人知识库(知识库、文档、批处理) | | 4 | `asl_schema` | 6个表 | ✅ 已实施 | AI智能文献(文献筛选) | | 5 | `dc_schema` | 6个表 | ✅ 已实施 | 数据清洗(模板、提取任务、Tool C) | | 6 | `admin_schema` | 0个表 | 📋 空Schema | 运营管理(预留) | | 7 | `rvw_schema` | 0个表 | 📋 空Schema | 审稿系统(预留) | | 8 | `ssa_schema` | 0个表 | 📋 空Schema | 智能统计分析(预留) | | 9 | `st_schema` | 0个表 | 📋 空Schema | 统计分析工具(预留) | | 10 | `common_schema` | 0个表 | 📋 空Schema | 通用能力层(预留) | | 11 | `public` | 4个表 | ⚠️ 旧表遗留 | _prisma_migrations, admin_logs, review_tasks, users | **总计:34个表(30个在隔离Schema中,4个在public中)** ### 3. 详细表清单 #### 3.1 platform_schema(8个表) ``` ✅ users - 用户表(3条记录) ✅ app_cache - Postgres-Only缓存(替代Redis) ✅ job - pg-boss任务表 ✅ job_common - pg-boss任务通用表 ✅ queue - pg-boss队列表 ✅ schedule - pg-boss定时任务表 ✅ subscription - pg-boss订阅表 ✅ version - pg-boss版本表 ``` **⚠️ 重要发现:pg-boss的6个表(job/queue等)是自动创建的,不在Prisma Schema中!** #### 3.2 aia_schema(5个表) ``` ✅ projects - 项目管理(2条记录) ✅ conversations - 项目对话 ✅ messages - 对话消息 ✅ general_conversations - 通用对话 ✅ general_messages - 通用消息 ``` #### 3.3 pkb_schema(5个表) ``` ✅ knowledge_bases - 知识库(2条记录) ✅ documents - 文档 ✅ batch_tasks - 批处理任务 ✅ batch_results - 批处理结果(976 KB) ✅ task_templates - 任务模板 ``` #### 3.4 asl_schema(6个表) ``` ✅ screening_projects - 文献筛选项目(18条记录) ✅ literatures - 文献数据(2.9 MB) ✅ screening_results - 筛选结果(1.2 MB) ✅ screening_tasks - 筛选任务 ✅ fulltext_screening_tasks - 全文筛选任务 ✅ fulltext_screening_results - 全文筛选结果 ``` #### 3.5 dc_schema(6个表) ``` ✅ dc_health_checks - 健康检查 ✅ dc_templates - 数据清洗模板(4条记录,424 KB) ✅ dc_extraction_tasks - 提取任务(728 KB) ✅ dc_extraction_items - 提取项(6.5 MB,最大表) ✅ dc_tool_c_sessions - Tool C会话(960 KB) ✅ dc_tool_c_ai_history - Tool C AI历史(1 MB) ``` #### 3.6 public schema(4个表)⚠️ ``` ⚠️ _prisma_migrations - Prisma迁移记录(6条记录) ⚠️ admin_logs - 管理日志(遗留) ⚠️ review_tasks - 审查任务(632 KB,遗留) ⚠️ users - 用户表(遗留,与platform_schema.users重复) ``` **说明:** - `public.users` 是Schema迁移前的旧表,与 `platform_schema.users` 结构完全相同 - `admin_logs` 和 `review_tasks` 应该迁移到 `admin_schema` 和 `rvw_schema` - `_prisma_migrations` 应该保留在 `public`,这是Prisma的标准位置 ### 4. 数据量统计(Top 15大表) | Schema | 表名 | 大小 | 说明 | |--------|------|------|------| | dc_schema | dc_extraction_items | 6.5 MB | 数据清洗提取项(最大表) | | asl_schema | literatures | 2.9 MB | 文献数据 | | asl_schema | screening_results | 1.2 MB | 筛选结果 | | dc_schema | dc_tool_c_ai_history | 1 MB | Tool C AI历史 | | pkb_schema | batch_results | 976 KB | 批处理结果 | | dc_schema | dc_tool_c_sessions | 960 KB | Tool C会话 | | dc_schema | dc_extraction_tasks | 728 KB | 提取任务 | | public | review_tasks | 632 KB | 审查任务(遗留) | | dc_schema | dc_templates | 424 KB | 数据清洗模板 | | pkb_schema | documents | 296 KB | 文档 | | aia_schema | general_messages | 208 KB | 通用消息 | | asl_schema | screening_projects | 144 KB | 筛选项目 | | aia_schema | conversations | 112 KB | 对话 | | pkb_schema | batch_tasks | 112 KB | 批处理任务 | | aia_schema | messages | 104 KB | 消息 | **总结:数据主要集中在 DC(数据清洗)和 ASL(文献筛选)模块。** ### 5. Prisma迁移历史 ```sql -- 6次Prisma迁移记录(按时间顺序) 20251010075003_init -- 2025-10-12(初始化) 20251010122727_add_conversation_metadata -- 2025-10-12(对话元数据) 20251012124747_add_batch_processing_module -- 2025-10-12(批处理模块) 20251014120128_add_review_tasks -- 2025-10-14(审查任务) 20251127_add_dc_tool_b_tables -- 2025-11-27(DC Tool B) 20251208_add_column_mapping -- 2025-12-08(列映射) ``` **说明:** - 最早的迁移(init)创建的是 `public` schema 中的表 - 后续通过手工SQL迁移到了10个隔离Schema - Prisma并没有记录Schema迁移过程(这些是手工SQL完成的) --- ## Prisma与数据库的差异分析 ### 1. pg-boss表的"自愈"机制✅(无需担心) **现象:** `platform_schema` 有 8 个表,但 Prisma Schema 只定义了 2 个: - ✅ Prisma定义:`AppCache`, `User` - 🔧 pg-boss自动管理:`job`, `job_common`, `queue`, `schedule`, `subscription`, `version`(共6个表) **原因:** 这6个表是 `pg-boss` 库在**应用启动时自动创建**的(用于Postgres-Only任务队列),不需要在Prisma Schema中定义。 **pg-boss的"自愈"能力:** ```typescript // backend启动时,pg-boss会自动检查表是否存在 const boss = new PgBoss(process.env.DATABASE_URL) await boss.start() // ✅ 如果表不存在,pg-boss会自动创建 // ✅ 只要数据库用户(如aiclinical_rw)有 CREATE TABLE 权限 // ✅ 完全不需要手工干预 ``` **影响评估:** - ✅ **运行时零影响**:pg-boss自己管理这些表,不通过Prisma访问 - ✅ **首次部署自动创建**:RDS部署时,backend启动会自动创建这6个表 - ⚠️ **Prisma db pull会检测到**:执行 `npx prisma db pull` 时会发现"未定义的表"(可忽略) - ⚠️ **Prisma migrate不操作**:迁移不会同步这些表(这是正确的) **最佳实践:** ```prisma // backend/prisma/schema.prisma // 在文件开头添加注释(文档用途) // ==================== pg-boss 自动管理的表(不需要定义) ==================== // 以下6个表由pg-boss库自动创建和维护,请勿手工修改: // - platform_schema.job - 任务表 // - platform_schema.job_common - 任务通用配置 // - platform_schema.queue - 队列表 // - platform_schema.schedule - 定时任务表 // - platform_schema.subscription - 订阅表 // - platform_schema.version - 版本表 // ==================== 以上表自动管理,无需Prisma定义 ==================== ``` **结论:pg-boss表"缺失"是正常的,无需担心!** ### 2. public schema遗留表⚠️(需要清理) **问题:** `public` schema 有 4 个表: - `_prisma_migrations`:Prisma标准表,正确✅ - `users`:与 `platform_schema.users` 重复❌ - `admin_logs`:应迁移到 `admin_schema`❌ - `review_tasks`:应迁移到 `rvw_schema`❌ **影响:** - ⚠️ 数据不一致风险(如果代码误读 `public.users`) - ⚠️ 混淆(两个 `users` 表) - ⚠️ 空间浪费(重复数据) **解决方案:** ```sql -- 步骤1:迁移 admin_logs 到 admin_schema ALTER TABLE public.admin_logs SET SCHEMA admin_schema; -- 步骤2:迁移 review_tasks 到 rvw_schema ALTER TABLE public.review_tasks SET SCHEMA rvw_schema; -- 步骤3:验证 platform_schema.users 和 public.users 数据一致后删除 -- (先在生产环境验证一周,确保代码不再引用 public.users) DROP TABLE public.users; ``` ### 3. Prisma Schema的定义状态 | Schema | Prisma定义的模型 | 实际数据库表 | 状态 | |--------|-----------------|-------------|------| | platform_schema | 2个(AppCache, User) | 8个(+6个pg-boss自动管理) | ✅ 正常(pg-boss自愈) | | aia_schema | 5个 | 5个 | ✅ 一致 | | pkb_schema | 5个 | 5个 | ✅ 一致 | | asl_schema | 6个 | 6个 | ✅ 一致 | | dc_schema | 6个 | 6个 | ✅ 一致 | | public | 2个(AdminLog, ReviewTask) | 4个(+_prisma_migrations, users) | ⚠️ 需清理 | **结论:** - ✅ **Prisma Schema 完全准确**:pg-boss的6个表会自动创建,不需要定义 - ⚠️ **public schema需要清理**:遗留了旧的 `users` 表(首次部署后清理) --- ## 代码如何连接数据库 ### 1. 环境变量配置 #### 1.1 默认连接字符串(env.ts) ```typescript // AIclinicalresearch/backend/src/config/env.ts (line 50) databaseUrl: process.env.DATABASE_URL || 'postgresql://postgres:postgres@localhost:5432/ai_clinical' ``` **⚠️ 注意:** - 默认值是 `ai_clinical`(不存在) - 实际数据库名是 `ai_clinical_research` - 说明你的 `.env` 文件中已经正确配置了 `DATABASE_URL` #### 1.2 连接池配置(database.ts) ```typescript // AIclinicalresearch/backend/src/config/database.ts export const prisma = new PrismaClient({ datasources: { db: { url: process.env.DATABASE_URL, // 从环境变量读取 }, }, }) // 连接池计算公式 connectionLimit = Math.floor(RDS_MAX_CONNECTIONS / MAX_INSTANCES) - 10 // 示例:400 / 20 - 10 = 10个连接/实例 ``` **云原生连接池策略:** ``` DATABASE_URL=postgresql://user:pass@host:5432/db?connection_limit=10&pool_timeout=10 ``` ### 2. 连接方式 | 连接参数 | 本地开发 | 阿里云SAE | |---------|---------|----------| | **主机** | localhost | RDS内网地址(如 rm-xxxxx.pg.rds.aliyuncs.com) | | **端口** | 5432 | 5432 | | **数据库** | ai_clinical_research | ai_clinical_research(保持一致) | | **用户名** | postgres | 自定义(如 aiclinical_rw) | | **密码** | postgres | 强密码(RDS创建时设置) | | **连接限制** | 无限制 | connection_limit=10(SAE每实例) | | **连接超时** | 默认 | pool_timeout=10 | ### 3. 多Schema访问(Prisma) ```prisma // AIclinicalresearch/backend/prisma/schema.prisma datasource db { provider = "postgresql" url = env("DATABASE_URL") schemas = ["platform_schema", "aia_schema", "pkb_schema", "asl_schema", "common_schema", "dc_schema", "rvw_schema", "admin_schema", "ssa_schema", "st_schema", "public"] } generator client { provider = "prisma-client-js" previewFeatures = ["multiSchema"] // 启用多Schema支持 } ``` **代码中的使用:** ```typescript // 自动路由到正确的Schema await prisma.user.findMany() // 访问 platform_schema.users await prisma.project.findMany() // 访问 aia_schema.projects ``` ### 4. 原生SQL访问(跨Schema) ```typescript // 支持跨Schema查询 const result = await prisma.$queryRaw` SELECT u.name, p.name AS project_name FROM platform_schema.users u JOIN aia_schema.projects p ON p.user_id = u.id ` ``` ### 5. 优雅关闭机制 ```typescript // AIclinicalresearch/backend/src/config/database.ts process.on('SIGTERM', () => gracefulShutdown('SIGTERM')) // SAE实例停止 process.on('SIGINT', () => gracefulShutdown('SIGINT')) // Ctrl+C async function gracefulShutdown(signal: string): Promise { await prisma.$disconnect() // 关闭所有连接 process.exit(0) } ``` **这是云原生最佳实践**,防止SAE扩容/缩容时连接泄漏。 --- ## 首次部署方案 ### 方案对比 | 方案 | 方法 | 优点 | 缺点 | 推荐度 | |------|------|------|------|--------| | **方案A** | `pg_dump` 全量导入 | ✅ 100%完整(包括pg-boss表)
✅ 结构+数据+索引+外键
✅ 一次性完成 | ❌ 需要手工清理测试数据
❌ 包含遗留的public表 | ⭐⭐⭐⭐⭐ **强烈推荐** | | **方案B** | Prisma Migrate Deploy | ✅ 版本化管理
✅ 可重复执行 | ❌ 缺少pg-boss表(运行时会报错)
❌ 需要手工补充 | ⚠️ **不推荐**(不完整) | | **方案C** | 手工SQL脚本 | ✅ 完全可控 | ❌ 工作量大
❌ 容易出错 | ⚠️ **不推荐**(费时费力) | ### ⭐ 推荐方案A:pg_dump全量导入(详细步骤) #### 步骤1:本地数据库清理(可选) ```bash # 如果本地有测试数据,可以选择清理 docker exec -it ai-clinical-postgres psql -U postgres -d ai_clinical_research # 删除测试用户(保留真实用户) DELETE FROM platform_schema.users WHERE email LIKE '%test%'; # 清理遗留的 public.users(Schema迁移后的旧表) DROP TABLE IF EXISTS public.users; ``` #### 步骤2:导出数据库(包含结构+数据) ```bash # 完整导出(包括所有Schema、表、数据、索引、外键) docker exec ai-clinical-postgres pg_dump -U postgres -d ai_clinical_research \ --format=plain \ --no-owner \ --no-acl \ --encoding=UTF8 \ > D:\MyCursor\ai_clinical_research_backup_$(Get-Date -Format 'yyyyMMdd_HHmmss').sql # 文件大小应该在 100-200 KB(当前数据量26MB) ``` **参数说明:** - `--format=plain`:纯文本SQL(方便查看和编辑) - `--no-owner`:不导出所有者信息(避免RDS用户名不匹配) - `--no-acl`:不导出权限信息(使用RDS默认权限) - `--encoding=UTF8`:UTF-8编码(中文支持) #### 步骤3:RDS PostgreSQL 15 准备 ```bash # 1. 在阿里云控制台创建RDS PostgreSQL 15实例 规格:rds.pg.s2.large(2核4GB,入门级) 存储:100GB SSD(支持自动扩容) 版本:PostgreSQL 15 网络:VPC(与SAE在同一VPC) 白名单:⚠️ 必须配置VPC网段,不能用单机IP! - 示例:172.16.0.0/12(SAE的VPC网段) - 查看方式:SAE控制台 > 应用详情 > 网络配置 > VPC网段 - ❌ 错误示例:172.16.1.23(单机IP,SAE实例IP会变化) # 2. 创建数据库 CREATE DATABASE ai_clinical_research WITH ENCODING='UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8' TEMPLATE=template0; # 3. 创建应用用户(不要用超级用户) CREATE USER aiclinical_rw WITH PASSWORD '你的强密码'; GRANT ALL PRIVILEGES ON DATABASE ai_clinical_research TO aiclinical_rw; GRANT ALL ON SCHEMA public TO aiclinical_rw; ``` #### 步骤4:导入到RDS ```bash # 方法1:从本地直接导入(需要RDS公网地址) psql -h rm-xxxxx.pg.rds.aliyuncs.com \ -p 5432 \ -U aiclinical_rw \ -d ai_clinical_research \ -f ai_clinical_research_backup_20251214_150000.sql # 方法2:通过ECS跳板机导入(推荐,更安全) scp backup.sql root@your-ecs-ip:/tmp/ ssh root@your-ecs-ip psql -h rm-xxxxx.pg.rds.aliyuncs.com -U aiclinical_rw -d ai_clinical_research -f /tmp/backup.sql ``` #### 步骤5:验证导入结果 ```sql -- 1. 验证Schema数量 SELECT nspname FROM pg_namespace WHERE nspname LIKE '%_schema' ORDER BY nspname; -- 应该有10个Schema -- 2. 验证表数量 SELECT schemaname, COUNT(*) as table_count FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema') GROUP BY schemaname ORDER BY schemaname; -- platform_schema: 8, aia_schema: 5, pkb_schema: 5, asl_schema: 6, dc_schema: 6 -- 3. 验证数据量 SELECT COUNT(*) FROM platform_schema.users; -- 应该有3条(或你保留的数量) SELECT COUNT(*) FROM aia_schema.projects; -- 应该有2条 SELECT COUNT(*) FROM asl_schema.literatures; -- 应该有对应数量 -- 4. 验证pg-boss表(关键) SELECT tablename FROM pg_tables WHERE schemaname = 'platform_schema' ORDER BY tablename; -- 应该包括:job, job_common, queue, schedule, subscription, version -- 5. 验证外键约束 SELECT conname, conrelid::regclass, confrelid::regclass FROM pg_constraint WHERE contype = 'f' LIMIT 10; ``` #### 步骤6:配置SAE环境变量 ```bash # 在SAE应用配置中添加 DATABASE_URL=postgresql://aiclinical_rw:强密码@rm-xxxxx.pg.rds.aliyuncs.com:5432/ai_clinical_research?connection_limit=10&pool_timeout=10&connect_timeout=10 # 其他相关配置 DB_MAX_CONNECTIONS=400 # RDS最大连接数 MAX_INSTANCES=20 # SAE最大实例数 NODE_ENV=production ``` #### 步骤7:应用启动验证 ```typescript // backend启动时会自动执行 npm run build npm run start // 日志输出应该显示: // ✅ 数据库连接成功! // 📊 数据库版本: PostgreSQL 15.x // 📊 当前数据库连接数: 1 ``` #### 步骤8:Prisma Client生成(重要) ```bash # 在backend目录下 cd backend npx prisma generate # 验证生成的Client ls node_modules/.prisma/client/ # 应该包含所有Schema的类型定义 ``` ### ⚠️ 方案B为什么不推荐:Prisma Migrate的局限 ```bash # 如果执行 prisma migrate deploy cd backend npx prisma migrate deploy # 问题1:缺少 pg-boss 表 # prisma migrate deploy 只会创建 Prisma Schema 中定义的表 # 不会创建 pg-boss 的 6 个表(job, queue等) # 问题2:应用启动时报错 # Error: relation "platform_schema.job" does not exist # 因为代码中使用了 pg-boss,但表不存在 # 问题3:需要手工补救 # 必须手工执行 pg-boss 的初始化SQL # 非常繁琐且容易出错 ``` **结论:`pg_dump` 全量导入是首次部署的最佳方案!** --- ## 未来更新策略 ### 场景1:新增一个表或修改一个字段(小更新) #### 推荐方案:Prisma Migrate(版本化管理) ```bash # 步骤1:在本地开发环境修改 schema.prisma # 示例:为 User 表添加一个字段 model User { id String @id @default(uuid()) email String @unique // ... 其他字段 ... phoneNumber String? @map("phone_number") // 新增字段 @@map("users") @@schema("platform_schema") } # 步骤2:创建迁移(本地) cd backend npx prisma migrate dev --name add_user_phone_number # 生成的迁移文件示例: # backend/prisma/migrations/20251214120000_add_user_phone_number/migration.sql ALTER TABLE "platform_schema"."users" ADD COLUMN "phone_number" TEXT; # 步骤3:测试本地迁移 npm run dev # 验证新字段可用 # 步骤4:提交到Git git add backend/prisma/migrations/20251214120000_add_user_phone_number git commit -m "feat: add phone_number to User model" git push # 步骤5:部署到SAE(自动执行迁移) # 在 SAE 的应用启动命令中添加: # CMD ["sh", "-c", "npx prisma migrate deploy && node dist/index.js"] # ⚠️ 注意:Dockerfile 的 CMD 应该是: CMD ["sh", "-c", "node dist/index.js"] # 不要在生产启动时执行 migrate deploy(风险太大) # 步骤6:手工执行迁移(推荐) # 方法1:通过ECS跳板机执行 ssh root@your-ecs-ip psql -h rm-xxxxx.pg.rds.aliyuncs.com -U aiclinical_rw -d ai_clinical_research \ -f migrations/20251214120000_add_user_phone_number/migration.sql # 方法2:使用 RDS 控制台的 SQL 窗口 # 直接粘贴迁移SQL执行 # 步骤7:验证 SELECT column_name, data_type FROM information_schema.columns WHERE table_schema = 'platform_schema' AND table_name = 'users' AND column_name = 'phone_number'; ``` **为什么不推荐在启动命令中执行 `prisma migrate deploy`?** 1. **并发风险**:SAE有多个实例,可能同时执行迁移 2. **回滚困难**:如果迁移失败,应用已经启动 3. **数据安全**:生产数据库应该手工迁移,有备份和回滚计划 ### 场景2:新增一个大模块(如SSA智能统计分析) #### 推荐方案:结构化迁移流程 ```bash # 步骤1:在本地开发环境设计Schema # 示例:SSA(智能统计分析)模块 # backend/prisma/schema.prisma // SSA Schema(新增) model SsaAnalysisTask { id String @id @default(uuid()) userId String @map("user_id") projectId String @map("project_id") taskName String @map("task_name") status String @default("pending") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") user User @relation(fields: [userId], references: [id]) results SsaAnalysisResult[] @@map("analysis_tasks") @@schema("ssa_schema") } model SsaAnalysisResult { id String @id @default(uuid()) taskId String @map("task_id") resultType String @map("result_type") resultData Json @map("result_data") createdAt DateTime @default(now()) @map("created_at") task SsaAnalysisTask @relation(fields: [taskId], references: [id]) @@map("analysis_results") @@schema("ssa_schema") } # 步骤2:创建迁移 npx prisma migrate dev --name add_ssa_module # 步骤3:验证迁移SQL # backend/prisma/migrations/20251214_add_ssa_module/migration.sql -- 检查: -- 1. 是否在 ssa_schema 中创建表 -- 2. 外键是否正确引用 platform_schema.users -- 3. 索引是否完整 # 步骤4:本地测试 # 开发SSA模块的业务逻辑 # 确保所有CRUD操作正常 # 步骤5:准备生产部署 # 5.1 数据库备份(强制) # 在RDS控制台手动创建快照:ai_clinical_research_before_ssa_module_20251214 # 5.2 准备回滚脚本 # backend/prisma/migrations/20251214_add_ssa_module/rollback.sql DROP TABLE IF EXISTS ssa_schema.analysis_results CASCADE; DROP TABLE IF EXISTS ssa_schema.analysis_tasks CASCADE; # 步骤6:生产环境迁移(分步执行) # 6.1 停机窗口(可选,如果无法零停机) # 晚上11点-凌晨2点,用户最少 # 6.2 执行迁移SQL psql -h rm-xxxxx.pg.rds.aliyuncs.com -U aiclinical_rw -d ai_clinical_research \ -f migrations/20251214_add_ssa_module/migration.sql # 6.3 验证表结构 \dt ssa_schema.* \d ssa_schema.analysis_tasks \d ssa_schema.analysis_results # 6.4 验证外键 SELECT conname, conrelid::regclass, confrelid::regclass FROM pg_constraint WHERE connamespace = 'ssa_schema'::regnamespace; # 步骤7:部署应用代码 # 7.1 更新backend git pull npm run build # SAE自动部署或手动触发部署 # 7.2 健康检查 curl https://your-backend.sae.aliyuncs.com/health # 应该返回 200 OK # 步骤8:烟雾测试(Smoke Testing) # 8.1 测试SSA模块基本功能 POST /api/v1/ssa/analysis-tasks GET /api/v1/ssa/analysis-tasks/:id # 8.2 测试旧模块(回归测试) GET /api/v1/aia/projects GET /api/v1/pkb/knowledge-bases # 确保旧功能不受影响 # 步骤9:监控24小时 # 9.1 RDS监控 # - 连接数是否正常 # - CPU/内存使用率 # - 慢查询日志 # 9.2 SAE监控 # - 应用日志(错误数量) # - API响应时间 # - 实例健康状态 # 步骤10:如果失败,立即回滚 # 10.1 回滚数据库 psql -h rm-xxxxx.pg.rds.aliyuncs.com -U aiclinical_rw -d ai_clinical_research \ -f migrations/20251214_add_ssa_module/rollback.sql # 10.2 回滚应用代码 # 在SAE控制台选择上一个版本,点击"回滚" # 10.3 恢复RDS快照(如果必要) # 在RDS控制台恢复快照:ai_clinical_research_before_ssa_module_20251214 ``` ### 场景3:紧急修复(Hotfix) ```bash # 示例:production环境发现某个字段长度不够 # 步骤1:本地创建紧急迁移 # backend/prisma/schema.prisma model User { // 将 email 字段长度从 varchar(255) 改为 text email String @unique // Prisma默认是text,无需修改 // ... } # 步骤2:生成迁移 npx prisma migrate dev --name hotfix_user_email_length # 步骤3:测试迁移SQL(本地) # 确保不会丢失数据 # 步骤4:生产环境执行(快速通道) # 4.1 备份(快照) # RDS控制台 > 备份 > 立即备份 > 备注:hotfix_before_email_length # 4.2 执行SQL(通过SQL窗口,秒级完成) ALTER TABLE platform_schema.users ALTER COLUMN email TYPE TEXT; # 4.3 验证 SELECT column_name, data_type, character_maximum_length FROM information_schema.columns WHERE table_schema = 'platform_schema' AND table_name = 'users' AND column_name = 'email'; # 4.4 提交Git git add backend/prisma/migrations/20251214_hotfix_user_email_length git commit -m "hotfix: increase user email column length" git push ``` --- ## RDS备份策略 ### 1. 阿里云RDS自动备份(强烈推荐)✅ #### 1.1 配置自动备份 ```bash # 在RDS控制台配置 备份设置 > 备份策略 # 推荐配置: 数据备份保留时间:7天(免费额度) 日志备份保留时间:7天 备份周期:每天一次 备份时间:凌晨2:00-4:00(业务低峰期) 备份方式:物理备份(快,占用空间小) ``` **优势:** - ✅ 全自动,无需人工干预 - ✅ 时间点恢复(Point-in-Time Recovery,PITR),可恢复到任意秒 - ✅ 存储在OSS,独立于RDS实例 - ✅ 免费(7天内) #### 1.2 手动快照(重要操作前) ```bash # 场景: # - 大版本升级前 # - 数据库Schema重大变更前 # - 删除大量数据前 # 操作: RDS控制台 > 备份恢复 > 备份实例 > 创建备份 备份方式:物理备份 备注:migration_before_ssa_module_20251214 ``` **快照保留建议:** - 迁移前快照:保留30天 - 版本升级前快照:保留60天 - 季度归档快照:保留1年 ### 2. 逻辑备份(`pg_dump`)- 可选策略(业务做大后再考虑) **⚠️ 初期建议:不需要自己写脚本,RDS自动备份就够了!** | 阶段 | 备份策略 | 理由 | |------|---------|------| | **初期(当前)** | ✅ 只用RDS自动备份 | - 自动备份+日志备份=PITR(任意时间点恢复)
- 阿里云托管,稳定可靠
- 无需维护脚本和ECS | | **成长期** | 可选:增加异地灾备 | - 业务关键后,考虑多地域备份
- 使用RDS的"跨地域备份"功能(一键配置) | | **成熟期** | 可选:自定义备份 | - 需要特殊备份策略(如每小时备份)
- 需要长期归档(如5年) | **如果未来需要自定义备份,参考脚本:** ```bash # ⚠️ 仅供参考,初期不需要执行 #!/bin/bash DATE=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="/data/pg_backups" RDS_HOST="rm-xxxxx.pg.rds.aliyuncs.com" RDS_USER="aiclinical_rw" RDS_DB="ai_clinical_research" # 导出SQL pg_dump -h $RDS_HOST -U $RDS_USER -d $RDS_DB \ --format=plain \ --no-owner \ --no-acl \ --encoding=UTF8 \ --file=$BACKUP_DIR/ai_clinical_research_$DATE.sql # 压缩并上传到OSS gzip $BACKUP_DIR/ai_clinical_research_$DATE.sql ossutil cp $BACKUP_DIR/ai_clinical_research_$DATE.sql.gz \ oss://your-bucket/database-backups/ # 清理本地备份 ls -t $BACKUP_DIR/*.sql.gz | tail -n +4 | xargs rm -f ``` **总结:初期专注RDS自动备份,省心省力!** ### 3. 恢复演练(每季度一次) ```bash # 目的:验证备份可用,熟悉恢复流程 # 步骤1:创建测试RDS实例 # RDS控制台 > 创建实例 > 按备份创建 # 选择最新的自动备份或快照 # 步骤2:验证数据完整性 psql -h test-rm-xxxxx.pg.rds.aliyuncs.com -U aiclinical_rw -d ai_clinical_research # 检查: SELECT schemaname, COUNT(*) FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema') GROUP BY schemaname; SELECT COUNT(*) FROM platform_schema.users; # 步骤3:记录恢复时间 # 26MB数据库,完整恢复约5-10分钟 # 步骤4:删除测试实例 ``` ### 4. 备份监控与告警 ```bash # 在阿里云监控配置告警规则 # 规则1:备份失败告警 资源:RDS实例 指标:备份任务状态 条件:备份失败 通知:钉钉群 + 邮件 # 规则2:备份空间不足告警 资源:RDS实例 指标:备份使用空间 条件:>80% 通知:邮件 # 规则3:超过24小时未备份告警 资源:RDS实例 指标:最后备份时间 条件:>24小时 通知:紧急(短信+电话) ``` --- ## 最佳实践与禁止操作 ### ✅ 最佳实践(11条黄金法则) #### 1. 连接池管理 ```typescript // ✅ 正确:在 DATABASE_URL 中配置连接限制 DATABASE_URL=postgresql://user:pass@host:5432/db?connection_limit=10&pool_timeout=10 // ✅ 正确:根据SAE实例数动态计算 connectionLimit = Math.floor(400 / 20) - 10 = 10 // ❌ 错误:不配置连接限制(会耗尽RDS连接) DATABASE_URL=postgresql://user:pass@host:5432/db ``` #### 2. 优雅关闭 ```typescript // ✅ 正确:监听 SIGTERM 信号 process.on('SIGTERM', async () => { await prisma.$disconnect() process.exit(0) }) // ❌ 错误:不关闭连接(SAE缩容时连接泄漏) ``` #### 3. 事务管理 ```typescript // ✅ 正确:使用Prisma事务 await prisma.$transaction(async (tx) => { await tx.user.create({ data: { email: 'test@example.com' } }) await tx.project.create({ data: { userId: 'xxx', name: 'Test' } }) }) // ❌ 错误:手动管理事务(容易忘记COMMIT) await prisma.$executeRaw`BEGIN` // ... 多个操作 ... await prisma.$executeRaw`COMMIT` // 如果中间出错,不会回滚 ``` #### 4. Schema隔离访问 ```typescript // ✅ 正确:Prisma自动路由到正确的Schema await prisma.user.findMany() // 自动访问 platform_schema.users // ✅ 正确:跨Schema查询(外键关系) await prisma.user.findUnique({ where: { id: 'xxx' }, include: { projects: true } // 自动JOIN aia_schema.projects }) // ❌ 错误:手写Schema名称(Prisma不推荐) await prisma.$queryRaw`SELECT * FROM platform_schema.users` // 可以工作,但类型不安全 ``` #### 5. 索引优化 ```sql -- ✅ 正确:为频繁查询的字段创建索引 CREATE INDEX idx_users_email ON platform_schema.users(email); CREATE INDEX idx_projects_user_id ON aia_schema.projects(user_id); -- ✅ 正确:为外键创建索引(Prisma不自动创建) CREATE INDEX idx_conversations_project_id ON aia_schema.conversations(project_id); -- ❌ 错误:创建过多索引(影响写入性能) -- 只为频繁查询的字段创建索引 ``` #### 6. 迁移测试 ```bash # ✅ 正确:先在测试环境验证 # 测试环境 > 预发环境 > 生产环境 # ✅ 正确:准备回滚脚本 # 每个迁移都有对应的rollback.sql # ❌ 错误:直接在生产环境执行未测试的迁移 ``` #### 7. 备份验证 ```bash # ✅ 正确:每季度恢复演练 # 确保备份可用 # ✅ 正确:重要操作前手动快照 # 大版本升级、Schema变更前 # ❌ 错误:从不验证备份(备份失效时才发现) ``` #### 8. 监控告警 ```bash # ✅ 正确:监控关键指标 - RDS连接数(告警阈值:80%) - 慢查询(告警阈值:>1秒) - 死锁(告警阈值:>0) - CPU使用率(告警阈值:>70%) # ❌ 错误:不监控(问题发生后才发现) ``` #### 9. 密码管理 ```bash # ✅ 正确:使用强密码(16位+大小写+数字+符号) DATABASE_URL=postgresql://user:Abc123!@#XYZ456@host:5432/db # ✅ 正确:定期轮换密码(每6个月) # ❌ 错误:使用弱密码(postgres/123456) ``` #### 10. 网络安全 ```bash # ✅ 正确:RDS只允许VPC内网访问 # 白名单配置:必须使用VPC网段,不能用单机IP 白名单示例:172.16.0.0/12(SAE的VPC网段) 获取方式:SAE控制台 > 应用详情 > 网络配置 > VPC网段 # ✅ 正确:不开放公网访问(除非临时调试) # ❌ 错误1:白名单配置 0.0.0.0/0(全世界可访问) # ❌ 错误2:白名单配置单机IP 172.16.1.23(SAE实例IP会变化,导致连接失败) # ❌ 错误3:配置多个单机IP(维护困难,且无法应对SAE弹性扩容) ``` #### 11. pg-boss表管理 ```typescript // ✅ 正确:不要在Prisma Schema中定义pg-boss表 // pg-boss自动管理,不需要手工定义 // ✅ 正确:不要手工修改pg-boss表 // 可能导致任务队列异常 // ❌ 错误:删除pg-boss表 // DROP TABLE platform_schema.job; // 会导致应用崩溃 ``` #### 12. 时区统一配置 ⭐⭐⭐⭐⭐ ```sql -- ✅ 正确:RDS时区配置为 Asia/Shanghai -- RDS控制台 > 参数设置 > timezone timezone = Asia/Shanghai -- ✅ 验证时区 SHOW timezone; -- 应该显示:Asia/Shanghai -- ❌ 错误:使用UTC时区(与应用时区不一致) -- 后果: -- 1. 日志时间对不上(前端14:00,数据库06:00) -- 2. pg-boss定时任务在错误时间触发 -- 3. 用户看到的时间戳错误 ``` **配置步骤:** ```bash # 步骤1:登录RDS控制台 # 阿里云 > 云数据库RDS > PostgreSQL实例 # 步骤2:修改参数 # 实例详情 > 参数设置 > 搜索 "timezone" # 修改为:Asia/Shanghai # 步骤3:重启实例(参数修改需要重启) # 实例详情 > 重启实例 # 步骤4:验证 psql -h rm-xxxxx.pg.rds.aliyuncs.com -U aiclinical_rw -d ai_clinical_research SHOW timezone; # 应该显示:Asia/Shanghai ``` **与应用时区保持一致:** ```bash # Node.js后端:ENV TZ=Asia/Shanghai # Python微服务:ENV TZ=Asia/Shanghai # 前端Nginx:ENV TZ=Asia/Shanghai # RDS PostgreSQL:timezone = Asia/Shanghai # ✅ 所有服务时区统一,避免时间混乱 ``` --- ### ❌ 绝对禁止的操作(10条红线) #### 🚫 1. 禁止在生产环境直接执行 `DROP TABLE` ```sql -- ❌ 绝对禁止 DROP TABLE platform_schema.users; -- 丢失所有用户数据,无法恢复 -- ✅ 正确做法:先备份,再重命名,观察7天后删除 ALTER TABLE platform_schema.users RENAME TO users_deprecated_20251214; -- 7天后确认无问题,再删除 DROP TABLE platform_schema.users_deprecated_20251214; ``` #### 🚫 2. 禁止在生产环境执行未测试的迁移 ```bash # ❌ 绝对禁止 psql -h production-rds -f untested_migration.sql # 可能破坏数据 # ✅ 正确做法:测试环境 > 预发环境 > 生产环境 ``` #### 🚫 3. 禁止不备份就执行重大变更 ```sql -- ❌ 绝对禁止 ALTER TABLE platform_schema.users DROP COLUMN email; -- 没有备份 -- ✅ 正确做法:先创建快照 -- RDS控制台 > 备份实例 > 创建备份 -- 然后再执行变更 ``` #### 🚫 4. 禁止在应用代码中硬编码数据库密码 ```typescript // ❌ 绝对禁止 const prisma = new PrismaClient({ datasources: { db: { url: 'postgresql://user:password@host:5432/db' // 硬编码 } } }) // ✅ 正确做法:使用环境变量 const prisma = new PrismaClient({ datasources: { db: { url: process.env.DATABASE_URL // 环境变量 } } }) ``` #### 🚫 5. 禁止不配置连接池限制 ```bash # ❌ 绝对禁止 DATABASE_URL=postgresql://user:pass@host:5432/db # 无限制 # ✅ 正确做法 DATABASE_URL=postgresql://user:pass@host:5432/db?connection_limit=10 ``` #### 🚫 6. 禁止在生产环境使用 `prisma migrate dev` ```bash # ❌ 绝对禁止 npx prisma migrate dev # 会删除数据重建 # ✅ 正确做法 npx prisma migrate deploy # 只应用新迁移,不删除数据 ``` #### 🚫 7. 禁止在生产环境启用Prisma的 `query` 日志 ```typescript // ❌ 绝对禁止(生产环境) export const prisma = new PrismaClient({ log: ['query', 'info', 'warn', 'error'] // 性能严重下降 }) // ✅ 正确做法(生产环境) export const prisma = new PrismaClient({ log: process.env.NODE_ENV === 'production' ? ['error'] : ['query', 'info', 'warn', 'error'] }) ``` #### 🚫 8. 禁止手工修改 `_prisma_migrations` 表 ```sql -- ❌ 绝对禁止 DELETE FROM _prisma_migrations WHERE migration_name = 'xxx'; -- 破坏迁移历史 -- ✅ 正确做法:如果迁移出错,回滚并修复 -- 不要手工修改 _prisma_migrations ``` #### 🚫 9. 禁止在生产环境使用超级用户(postgres) ```bash # ❌ 绝对禁止 DATABASE_URL=postgresql://postgres:password@host:5432/db # 权限过大 # ✅ 正确做法:创建应用专用用户 CREATE USER aiclinical_rw WITH PASSWORD 'xxx'; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA platform_schema TO aiclinical_rw; ``` #### 🚫 10. 禁止删除pg-boss的表 ```sql -- ❌ 绝对禁止 DROP TABLE platform_schema.job; -- 应用立即崩溃 DROP TABLE platform_schema.queue; -- 任务队列失效 -- ✅ 正确做法:永远不要手工操作pg-boss表 -- 如果需要清理,使用pg-boss的API ``` --- ## 📝 总结与建议 ### 1. 你的数据库现状(非常好)✅ | 维度 | 状态 | 评分 | |------|------|------| | **Schema隔离架构** | ✅ 10个Schema全部就位 | ⭐⭐⭐⭐⭐ | | **Postgres-Only架构** | ✅ pg-boss正常工作,支持自愈 | ⭐⭐⭐⭐⭐ | | **数据完整性** | ✅ 外键约束完整 | ⭐⭐⭐⭐⭐ | | **Prisma Schema** | ✅ 完全准确(pg-boss表会自动创建) | ⭐⭐⭐⭐⭐ | | **数据量** | ✅ 26MB,适合迁移 | ⭐⭐⭐⭐⭐ | | **遗留问题** | ⚠️ public schema有旧表(首次部署后清理) | ⭐⭐⭐⭐ | **总评:你的数据库架构非常优秀,可以直接部署到RDS!** ### 2. 首次部署推荐方案 ``` 🏆 强烈推荐:pg_dump 全量导入 理由: ✅ 100%完整(包括pg-boss表) ✅ 结构+数据+索引+外键一次性完成 ✅ 风险低,可重复执行 ✅ 恢复快(26MB约30秒) 时间估算: - 导出:1分钟 - 传输到ECS:1分钟 - 导入RDS:3分钟 - 验证:2分钟 总计:<10分钟 ``` ### 3. 未来更新推荐方案 | 更新类型 | 推荐方案 | 工具 | |---------|---------|------| | 小更新(新增字段) | Prisma Migrate | `npx prisma migrate dev` | | 中更新(新增表) | Prisma Migrate | `npx prisma migrate dev` | | 大更新(新模块) | 结构化流程 | Prisma + 手工SQL + 备份 | | 紧急修复(Hotfix) | 快速通道 | 直接SQL(先备份) | ### 4. 备份策略(初期简化版) ``` 第一道防线:RDS自动备份(必做)⭐⭐⭐⭐⭐ ├─ 数据备份:每天凌晨2点 ├─ 日志备份:实时(开启PITR) └─ 保留7天 第二道防线:手动快照(必做)⭐⭐⭐⭐ └─ 重大操作前创建 第三道防线:pg_dump脚本(初期不需要) └─ 业务做大后再考虑 恢复演练:每季度一次(必做) ``` **初期只需配置RDS自动备份,省心省力!** ### 5. 清理遗留问题(可选) ```sql -- 建议在首次部署到RDS后清理 -- 1. 迁移 admin_logs 到 admin_schema ALTER TABLE public.admin_logs SET SCHEMA admin_schema; -- 2. 迁移 review_tasks 到 rvw_schema ALTER TABLE public.review_tasks SET SCHEMA rvw_schema; -- 3. 删除重复的 public.users(验证后) -- 先对比数据一致性 SELECT COUNT(*) FROM public.users; SELECT COUNT(*) FROM platform_schema.users; -- 一致后删除 DROP TABLE public.users; -- 4. 更新Prisma Schema model AdminLog { // ... @@map("admin_logs") @@schema("admin_schema") // 从 public 改为 admin_schema } model ReviewTask { // ... @@map("review_tasks") @@schema("rvw_schema") // 从 public 改为 rvw_schema } ``` ### 6. 下一步行动 ``` ☐ 1. 创建阿里云RDS PostgreSQL 15实例 ☐ 2. 配置VPC、白名单(⚠️用VPC网段)、备份策略(自动备份+日志备份) ☐ 3. 本地清理测试数据(可选) ☐ 4. pg_dump导出数据库 ☐ 5. 通过ECS跳板机导入RDS ☐ 6. 验证导入结果(Schema、表、数据、pg-boss) ☐ 7. 配置SAE环境变量(DATABASE_URL) ☐ 8. 部署backend应用 ☐ 9. 端到端测试 ☐ 10. 监控24小时 预计时间:3-4小时(从0到上线) ``` --- **文档创建人:** AI助手 **最后更新:** 2025-12-14 **版本:** v1.0 **核心理念:数据安全第一,充分备份,渐进式迁移,完整验证** ⭐⭐⭐