Ver Fonte

docs: design datasource connection pooling

马小龙 há 5 dias atrás
pai
commit
30fc9cff22

+ 385 - 0
docs/superpowers/specs/2026-07-18-datasource-connection-pool-design.md

@@ -0,0 +1,385 @@
+# 数据源连接池与凭据安全设计
+
+日期:2026-07-18
+状态:已完成讨论,等待书面规格审阅
+
+## 1. 目标
+
+为 DataOps 平台接入的外部业务数据源建立统一、安全、可观测的连接池框架。
+第一阶段走通 PostgreSQL 和 MySQL,部署边界为单台服务器、单个后端实例,
+并为后续多副本部署保留配置与接口扩展能力。
+
+本设计明确区分两类数据库连接:
+
+- 平台控制库:DataOps 自身使用的 PostgreSQL,保存用户、订单、流程版本、
+  工作台、知识库等平台数据。继续由 Flask-SQLAlchemy 管理,不建设额外的
+  数据源连接池管理层。
+- 业务数据源:通过 `DataSource` 定义接入的外部数据库。元数据采集、数据预览、
+  质量检查和 DataFlow 执行通过本设计的连接池管理器访问。
+
+## 2. 范围
+
+### 2.1 第一阶段范围
+
+- PostgreSQL 数据源,驱动为 `postgresql+psycopg2`。
+- MySQL 数据源,驱动为 `mysql+pymysql`。
+- 按数据源延迟创建和复用连接池。
+- 空闲池回收、配置变更失效、删除时排空。
+- 数据源凭据加密保存和历史明文凭据迁移。
+- 故障隔离、熔断、指标和管理员诊断。
+- 全本地隔离 Docker 验收环境。
+
+### 2.2 暂不进入第一阶段
+
+- 多后端副本间的全局连接预算和共享状态。
+- PgBouncer 或其他外部连接代理。
+- Oracle、SQL Server、DB2、Sybase 等驱动的正式验收。
+- 跨实例分布式连接池、分布式锁或集中式连接代理。
+- 任意 SQLAlchemy driver name 和任意 `connect_args` 透传。
+
+## 3. 总体架构
+
+```text
+平台控制面
+Flask-SQLAlchemy ──> 平台 PostgreSQL
+仅使用框架连接管理,不进入 DataSource Pool Registry
+
+业务数据面
+DataSource API / 元数据采集 / 数据预览 / DataFlow
+                    │
+                    ▼
+          DataSourceConnectionManager
+          ├── DataSourceDefinitionRepository
+          ├── CredentialProvider
+          ├── PostgreSQLAdapter
+          ├── MySQLAdapter
+          ├── PoolRegistry
+          ├── CircuitBreaker
+          └── PoolMetrics
+                    │
+          ┌─────────┴─────────┐
+          ▼                   ▼
+ PostgreSQL 数据源池       MySQL 数据源池
+```
+
+业务模块只能通过 `DataSourceConnectionManager` 获取外部数据库连接,不得直接
+读取数据源密码并调用 `create_engine()`。连接测试是唯一允许创建临时 Engine 的
+入口,且必须使用 `NullPool` 并在完成后执行 `dispose()`。
+
+## 4. 数据源身份与连接池注册
+
+每个数据源必须具有稳定 UUID,并以 `data_source_uid` 作为业务身份。池注册键由
+以下内容组成:
+
+```text
+data_source_uid
+credential_version
+connection_config_fingerprint
+```
+
+连接配置指纹由数据库类型、主机、端口、数据库名、Schema 和白名单安全选项生成,
+不包含用户名、密码、连接 URL 或密文。
+
+连接池按需创建:
+
+- 第一次采集、预览或 DataFlow 使用时创建。
+- 同一 Gunicorn Worker 内,同一注册键复用一个 SQLAlchemy Engine。
+- 数据源定义、凭据版本或安全配置变化后,旧注册键失效。
+- 数据源删除后,禁止新连接借出并排空旧池。
+- 空闲 15 分钟的池执行回收。
+- 每个 Worker 默认最多缓存 20 个空闲池。
+- 正在执行任务的池进入 `draining`,连接归还后再释放。
+
+Gunicorn Worker 是独立进程。即使只有一个后端实例,各 Worker 仍拥有独立的
+Pool Registry,第一阶段不尝试跨进程共享数据库连接。
+
+## 5. 默认容量
+
+单个数据源、单个 Worker 的默认参数:
+
+```text
+pool_size=2
+max_overflow=3
+pool_timeout=10
+pool_recycle=1800
+pool_pre_ping=true
+pool_use_lifo=true
+```
+
+采用 4 个 Worker 时,一个持续繁忙的数据源理论最大连接数为 20,常驻连接数
+最大为 8。数据源可以配置受控的池大小覆盖值,但服务端必须限制上下限,不能直接
+接受用户提交的任意连接数量。
+
+后续多副本阶段使用以下公式重新计算单数据源连接预算:
+
+```text
+副本数 × Worker 数 × (pool_size + max_overflow)
+```
+
+## 6. 驱动适配
+
+Adapter 负责:
+
+- 校验数据源类型和连接字段。
+- 构建 SQLAlchemy URL。
+- 生成驱动专属白名单参数。
+- 创建 Engine。
+- 执行 `SELECT 1` 探测。
+- 将驱动异常转换为稳定的平台错误。
+- 提供数据库标识符引用和只读事务能力。
+
+PostgreSQL 默认启用 TCP keepalive:
+
+```text
+connect_timeout=5
+keepalives=1
+keepalives_idle=30
+keepalives_interval=10
+keepalives_count=3
+```
+
+MySQL 默认设置:
+
+```text
+connect_timeout=5
+read_timeout=30
+write_timeout=30
+charset=utf8mb4
+```
+
+SSL 和证书参数只能从明确白名单读取,不允许任意驱动参数透传。前端传入的
+`type` 必须映射到平台内部 Adapter,不能直接成为 SQLAlchemy driver name。
+
+## 7. 凭据模型
+
+平台 PostgreSQL 新增 `datasource_credentials`:
+
+```text
+id
+data_source_uid
+credential_version
+encrypted_payload
+nonce
+key_version
+status
+created_at
+retired_at
+```
+
+`encrypted_payload` 包含用户名、密码和必要的认证选项。Neo4j `DataSource` 节点
+只保存:
+
+```text
+uid
+type
+host
+port
+database
+schema
+credential_ref
+credential_version
+业务描述和治理属性
+```
+
+凭据使用 AES-256-GCM 加密:
+
+- 主密钥由 `DATASOURCE_CREDENTIAL_MASTER_KEY` 环境变量注入。
+- 每条凭据使用独立随机 nonce。
+- `data_source_uid + credential_version` 作为附加认证数据。
+- `key_version` 为后续主密钥轮换保留。
+- 主密钥不写入代码、数据库、镜像或日志。
+- 缺少主密钥或解密失败时,数据源功能失败关闭,但平台控制面仍可启动。
+
+API、日志、异常、Neo4j 查询结果和诊断接口均不得返回用户名、密码、密文、
+带凭据的 URL 或完整连接串。数据源列表只返回 `credential_configured` 等布尔状态。
+
+## 8. 数据源变更流程
+
+### 8.1 创建
+
+1. 校验类型,只允许 PostgreSQL 或 MySQL。
+2. 校验主机、端口、数据库名和安全选项。
+3. 使用 `NullPool` 临时 Engine 测试连接,随后关闭。
+4. 将凭据加密写入平台 PostgreSQL。
+5. 在 Neo4j 保存不含密码的数据源定义及凭据引用。
+6. 成功后允许 Pool Registry 按 UID 延迟创建连接池。
+
+### 8.2 修改
+
+1. 创建不可变的新凭据版本。
+2. 更新 Neo4j 数据源定义和 `credential_version`。
+3. 将旧连接池标记为 `draining`。
+4. 后续请求使用新注册键创建连接池。
+5. 旧凭据标记为 `retired`。
+
+修改失败时,数据源继续引用原有有效版本。PostgreSQL 与 Neo4j 之间使用现有
+Outbox 和补偿巡检实现最终一致性。
+
+### 8.3 删除
+
+1. 禁止新连接借出。
+2. 等待已借出连接在限定时间内归还。
+3. 到期后强制释放 Engine。
+4. 删除或归档 Neo4j 数据源定义。
+5. 将凭据标记为 `revoked`。
+6. 记录不含敏感数据的审计事件。
+
+## 9. 连接获取接口
+
+业务模块使用统一的上下文接口:
+
+```python
+with data_source_manager.connect(
+    data_source_uid,
+    purpose="metadata_preview",
+) as connection:
+    ...
+```
+
+管理器依次完成定义读取、凭据获取、Adapter 选择、池注册、连接借出和归还。
+业务模块不持有 Engine,不负责连接池配置,也不能访问凭据明文。
+
+`purpose` 使用平台枚举,第一阶段至少包括:
+
+- `connection_test`
+- `metadata_collection`
+- `metadata_preview`
+- `quality_check`
+- `dataflow_read`
+- `dataflow_write`
+
+除 `dataflow_write` 外默认使用只读事务。写入任务必须显式提交,异常时回滚。
+
+## 10. 故障隔离与熔断
+
+每个数据源独立维护状态:
+
+```text
+healthy
+degraded
+open
+draining
+closed
+```
+
+- 单个数据源连接耗尽或宕机,不影响平台控制库和其他数据源。
+- 连续 3 次建连失败后熔断 30 秒。
+- 熔断结束后只允许一个半开放探测请求。
+- 探测成功恢复 `healthy`,失败继续熔断。
+- 借出等待超过 10 秒返回 `DATASOURCE_POOL_TIMEOUT`。
+- 事务中断必须回滚,不自动重放数据源写操作。
+- 失效连接不再返回池中。
+
+主要稳定错误码:
+
+- `DATASOURCE_NOT_FOUND`
+- `DATASOURCE_TYPE_UNSUPPORTED`
+- `DATASOURCE_CREDENTIAL_UNAVAILABLE`
+- `DATASOURCE_CONNECTION_FAILED`
+- `DATASOURCE_POOL_TIMEOUT`
+- `DATASOURCE_CIRCUIT_OPEN`
+- `DATASOURCE_QUERY_TIMEOUT`
+- `DATASOURCE_READ_ONLY_VIOLATION`
+
+错误响应不包含驱动原始连接 URL 或凭据。
+
+## 11. 监控与管理
+
+管理员可以查看单个数据源的池摘要:
+
+```text
+data_source_uid
+database_type
+pool_state
+pool_size
+checked_out
+checked_in
+overflow
+checkout_wait_ms
+last_used_at
+consecutive_failures
+circuit_open_until
+credential_version
+```
+
+平台健康检查仅返回聚合数据:
+
+```text
+active_pool_count
+degraded_pool_count
+open_circuit_count
+checked_out_total
+pool_timeout_total
+```
+
+指标至少覆盖:
+
+- 连接创建成功和失败次数。
+- Checkout 等待时间。
+- 当前占用、空闲和溢出连接数。
+- 池超时、连接失效和重建次数。
+- 查询耗时。
+- 空闲池回收次数。
+- 凭据版本导致的池重建次数。
+
+所有日志使用数据源 UID 和错误分类;异常文本在写日志前统一脱敏。
+
+## 12. 历史凭据迁移
+
+迁移必须分两步:
+
+1. 只读报告:统计含明文凭据、缺少 UID 或配置不完整的数据源,只输出 UID 和问题
+   分类。
+2. 显式迁移:补齐 UID、加密凭据、写入凭据引用、验证可解密后删除 Neo4j 明文字段。
+
+生产执行前必须单独备份 Neo4j 和平台 PostgreSQL。迁移过程不得在日志、报告或
+异常中输出明文密码。迁移失败的数据源保持原状态并进入人工核查清单。
+
+现有数据源保存、验证和连接测试接口必须先完成请求日志脱敏。数据源列表接口必须
+在凭据迁移前就停止返回密码。
+
+## 13. 本地隔离验收
+
+Docker 测试栈新增两个业务数据源服务:
+
+```text
+platform-postgres  平台控制库
+source-postgres    PostgreSQL 验收数据源
+source-mysql       MySQL 验收数据源
+```
+
+验收标准:
+
+1. PostgreSQL、MySQL 均可保存、测试、建池和执行查询。
+2. 同一数据源并发请求复用连接且不超过池上限。
+3. 50–100 个并发请求下无连接泄漏。
+4. 配置或密码修改后旧池失效,新池使用新版本。
+5. 空闲池到期释放,再次访问可重建。
+6. MySQL 停机不影响 PostgreSQL 数据源和平台功能。
+7. 数据源恢复后可通过半开放探测恢复。
+8. 删除数据源后连接池和凭据不可继续使用。
+9. API、日志、Neo4j 和异常中不存在明文密码。
+10. 后端重启后连接池按需重建。
+
+测试包括:
+
+- AES-GCM 加密、篡改检测和密钥版本测试。
+- PostgreSQL、MySQL Adapter 合约测试。
+- Pool Registry 并发、回收和失效测试。
+- 熔断状态机测试。
+- 历史明文凭据迁移测试。
+- Docker 数据源宕机和恢复测试。
+- 前后端数据源管理接口验收。
+
+## 14. 后续扩展
+
+进入多副本阶段后再评估:
+
+- 按副本数重新计算连接预算。
+- PgBouncer。
+- 跨实例池状态聚合。
+- 数据源任务按 UID 路由。
+- Oracle、SQL Server 等额外 Adapter。
+
+第一阶段的 Adapter、CredentialProvider、Pool Registry 和指标接口保持稳定,
+多副本阶段不改变业务模块的连接获取接口。