# 数据源连接池与凭据安全设计

日期：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 和指标接口保持稳定，
多副本阶段不改变业务模块的连接获取接口。
