Files
qinglong/docs/adr/ADR-0038-cluster-driver-package-and-dependency-baseline.md
T

162 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-0038Cluster Driver 独立交付与 PostgreSQL 依赖基线
- 状态:Proposed
- 日期:2026-07-19
- 关联:QL-RFC-0001、ADR-0006、ADR-0037
## 上下文
QingLong 3.0 必须同时覆盖低性能路由设备和多副本集群。当前仓库只有一个根 `package.json`Alpine/Debian 镜像都把根生产依赖完整安装到 `/ql/node_modules`。因此仅使用动态 `import('pg')` 只能避免启动时加载,不能避免 edge 镜像下载、解压和保存 PostgreSQL driver。
当前交付基线还有三项不一致:
- legacy Alpine builder 使用 Node 18Debian 使用 Node 20;两者都不是 QL3 的 Node 24 目标;
- `ql3-ci.yml` 同时运行 Node 20/24,Node 20 只能作为迁移期兼容观察,不能继续定义 QL3 cluster runtime
- 根后端使用 CommonJS、TypeScript 5.2 和旧 `@types/node`,直接把新 ORM 类型面扩散到全部 `back/**` 会扩大迁移范围。
截至 2026-07-19 的上游事实是:
- Node 24.18.0 是 LTSNode 20 已 EOL
- `pg` 8.22.0 的官方兼容矩阵包含 Node 24,且 CommonJS 仍受支持;
- Drizzle ORM 0.45.2 是稳定版本,包含 identifier/alias escaping 安全修复;1.0 仍为 RC
- Drizzle Kit 0.31.10 是对应稳定工具线;
- PostgreSQL 官方仍支持 1418,但 14 将于 2026-11 EOL1618 更符合新 3.0 产品的维护窗口。
参考:
- [Node.js release schedule](https://nodejs.org/en/about/previous-releases)
- [Node.js 24.18.0 LTS](https://nodejs.org/en/blog/release/v24.18.0)
- [node-postgres compatibility](https://node-postgres.com/)
- [Drizzle PostgreSQL with node-postgres](https://orm.drizzle.team/docs/get-started-postgresql)
- [Drizzle releases](https://github.com/drizzle-team/drizzle-orm/releases)
- [PostgreSQL versioning policy](https://www.postgresql.org/support/versioning/)
## 决策
### 1. 固定首个 cluster vertical slice 的版本
首个可执行 vertical slice 使用 exact version,不使用 `^``~` 或 prerelease tag
| 组件 | 固定版本 | 用途 |
| --- | --- | --- |
| Node.js | `24.18.0` | cluster build、migration Job 与 runtime |
| `pg` | `8.22.0` | 纯 JavaScript Pool/Client driver |
| `drizzle-orm` | `0.45.2` | typed schema 与 Repository query builder |
| `drizzle-kit` | `0.31.10` | 仅开发期 schema diff、SQL export/check |
| `typescript` | `5.9.3` | cluster package 独立严格类型构建 |
| `@types/node` | `24.13.3` | cluster package 的 Node 24 类型边界 |
| `@types/pg` | `8.20.0` | cluster package 开发类型 |
| PostgreSQL | `16``18` | 生产支持范围;CI 覆盖最低与最高版本 |
不安装 `pg-native`,避免为 cluster driver 增加 libpq、编译器、musl/glibc 和多架构 native prebuild 边界。版本升级必须是独立 PR,附带生成 SQL diff、migration checksum 审计、双方言 contract suite、安装体积和 RSS 对比。
Node 20 只作为 2.x 兼容迁移的临时测试输入;新的 `pg-*` migration、cluster package 和 cluster-control readiness 不支持 Node 20。正式 QL3 镜像必须统一到固定 Node 24 patch,不能继续从发行版包管理器取得未固定的 `nodejs`
### 2. Cluster driver 是独立 package,不是根 optional dependency
目标 workspace 至少拆出:
```text
@whyour/qinglong legacy compatibility app / edge / standalone
@qinglong/cluster-postgres pg + Drizzle adapter 与 pg-* migration binding
@qinglong/cluster-control 显式组合 core 与 cluster-postgres 的集群入口
```
根 package 不把 `pg``drizzle-orm``@qinglong/cluster-postgres` 放入 `dependencies``optionalDependencies` 或自动安装的 peer。edge/standalone 发布物只能安装根 importercluster-control 发布物显式安装 cluster assembly importer。
动态 import 仍用于延迟初始化,但它不是依赖隔离的证据。隔离必须由 lockfile importer、profile-specific image stage 和产物内容审计共同证明。
在 workspace 拆分完成前,driver-neutral PostgreSQL migration Store 可以保留在公共 core 中,但只能依赖 QingLong 自己定义的 `Pool/Client/QueryResult` 端口;实际 `pg.Pool` 绑定只能存在于 cluster package。
### 3. Drizzle 管 schema/query,不接管生产 migration history
Drizzle schema 是类型与 SQL 生成输入,Drizzle Kit 只允许在开发/CI 执行:
- `generate`/`export` 产生待审查 SQL
- `check` 验证 snapshot 分支和生成结果;
- 生成 SQL 经 canonical 化后固定为 `pg-*` migration 内容并计算 SHA-256
- 生产 migration Job 只调用 QingLong 的 dialect-neutral migration stream。
生产环境禁止 `drizzle-kit push`、自动 introspection 后写库或独立 Drizzle migration history。否则会出现两个 history、不可审查 schema alter 和 runtime/migration role 混权。
### 4. Migration 与 runtime 使用不同 entrypoint 和 role
Cluster package 至少导出两个不共享 Secret 默认值的入口:
- `migration`:允许 DDL,取得固定 advisory lock,执行 `pg-*` stream 后退出;
- `runtime`:无 DDL 权限,只验证 history/schema capability,然后创建有界 Pool。
两者都必须配置 TLS 验证、`application_name`、固定 `search_path`、statement/lock/idle-in-transaction timeout。连接 URI 不进入日志、错误详情、RunEvent 或诊断 bundle。
### 5. Profile-specific 构建是发布门禁
edge/standalone 产物必须证明:
- `node_modules` 不包含 `pg`、Drizzle PostgreSQL entrypoint 或 cluster assembly package
- import graph 和 `require.cache` 不出现 cluster package
- 未创建 PostgreSQL DNS、socket、Pool、retry timer 或 Secret read
- 与加入 cluster workspace 前相比,安装体积、冷启动和 RSS 回归在预算内。
cluster-control 产物则必须证明它包含 exact 版本、没有 `pg-native`,并能在 PostgreSQL 16 与 18 上通过 migration/contract/fencing 测试。
## 当前孵化状态
`next` 已新增 driver-neutral `PostgresMigrationStreamStore`
- 公共代码不 import `pg`
- history bootstrap 与每条 migration 都在 PostgreSQL transaction 中取得固定双 int32 advisory lock
- lock 使用 `pg_try_advisory_xact_lock`,竞争者 fail closed,不无限等待;
- transaction 内设置 statement、lock 与 idle-in-transaction timeout
- history 表使用 `pg-*` ID、64 位小写 SHA-256、非负 bigint 时间;
- migration work 与 history insert 同事务提交,失败回滚;
- bigint 时间只允许 JavaScript safe integer,损坏 history fail closed。
- core 在任何新 DDL 前枚举 history,拒绝未知 ahead ID、非前缀缺口、重复记录和 checksum/stream/dialect 漂移;
- `pg-0001-schema-capability``pg-0002-run-core``pg-0003-run-retry-policy` 已建立首批 SQL stream,只创建 Run、RunAttempt、RunEvent、RunRetryPolicy 与 bounded indexes,不复制 local/legacy 表;
- 本机一次性 PostgreSQL 13 实例已实际验证三条 migration 的 SQL 语法、deferred capability/history 提交和最终 6 表集合;目标支持证据仍必须来自 PostgreSQL 16/18 CI。
当前 `next` 已创建 `packages/ql3-runtime-core``packages/ql3-cluster-postgres``packages/ql3-cluster-control` 三个独立 importer,并 exact pin/lock 全部 dependency section。workspace 禁止从根解析 peer,避免 Drizzle 因 legacy 根 `sqlite3` 形成隐式 native peer`@types/pg` 被定向固定到 Node 24 类型。根 importer 仍不声明 runtime-core、`pg`、Drizzle 或 cluster package。
`@qinglong/cluster-postgres` 已实现真实 `pg.Pool` 结构绑定和 lazy database opener:构造 opener/Resource 不建连接,readiness/Repository 首次 query 才连接;runtime/migration 使用独立 application name、Pool 上限、timeout 与显式 TLS 配置,固定 `search_path`bigint 继续按字符串解析,`pg-native` 未安装。本机 PostgreSQL 13 已额外通过完整共享 RunRepository contract 和双角色验证:runtime 可读取 history/capability 并执行 Run DML,但 DDL 以 `42501` 拒绝。PG13 仍只是额外语法/driver 下限,不改变 PostgreSQL 16/18 integration gate。
`@qinglong/cluster-postgres` 已增加六表 Drizzle typed schema,并与 reviewed schema contract 的 columns、18 个 index、39 个 CHECK 和 7 个 FK 做精确 metadata lockstep。包级 TypeScript 继续严格检查自身源码,但对 Drizzle 发布包中未安装的 Gel/MySQL/SQLite 可选方言声明启用 `skipLibCheck`;不得为消除第三方可选声明错误而把无关 driver 安装进 PostgreSQL 产物。生产仍只执行 QingLong `pg-*` migration stream。
PostgreSQL 16/18 × x64/arm64 CI matrix、真实 `pg.Pool` Repository contract、readiness 和双角色最小权限测试已经接线,远端矩阵成功证据仍待 CI 实际运行。`@qinglong/runtime-core` 已公开 migration/activation/RunRepository contractPostgreSQL migration、readiness 与 RunRepository 已迁入 `@qinglong/cluster-postgres`cluster integration 不再引用 legacy 根实现。cluster-postgres 使用受 `exports` 限制的 `runtime`/`migration` 子入口:前者只装入 history manifest、readiness、Pool 和 Repository,后者才装入 executable DDL streamlockstep test 固定两者 ID/checksum 一致。`@qinglong/cluster-control` 已声明独立 build/export,并实现 readiness-first 可执行组合根,在 readiness 成功后创建真实 PostgreSQL RunRepository,再把已证明的资源交给 production stack factory。当前缺口是完整 control-plane stack、admission/API、独立镜像/SBOM 和多副本故障演练,而不是 driver 或 Repository package 边界。typed schema 权威关系由 ADR-0043、兼容副本退出条件由 ADR-0044 继续约束。
## 影响
正面影响:
- edge 不为 cluster driver 支付磁盘、安装时间、RSS 或 native 供应链成本;
- 新 cluster 代码可以采用现代类型和 Node 24,而不一次性改写全部 legacy 后端;
- SQL 生成、不可变 history 和生产权限边界各自清晰;
- exact 版本与独立升级 PR 便于多架构复现和回滚。
代价与风险:
- 仓库从单 package 走向 workspace,镜像、缓存和发布流程必须同步改造;
- core/cluster assembly 的 API 边界需要稳定导出,不能依赖跨 package 深层相对路径;
- Drizzle 0.x 到未来 1.x 需要独立评估,不自动随 minor/RC 漂移;
- fake driver 测试无法发现 PostgreSQL catalog、锁、权限、TLS 和 failover 差异。
## 未选择的方案
1. **把 `pg`/Drizzle 直接加入根 dependencies**:所有 edge 镜像都会安装,拒绝。
2. **只做 dynamic import**:只能降低启动加载,不能降低产物体积,拒绝。
3. **把 driver 放 optionalDependencies**:当前所有镜像默认仍安装 optional dependency,拒绝。
4. **采用 Drizzle 1.0 RC**:基础契约仍可能破坏性变化,首个稳定 vertical slice 不采用。
5. **生产执行 `drizzle-kit push`**:绕过不可变 migration review/history 与最小权限,拒绝。
6. **安装 `pg-native` 获取约 10% 性能收益**:收益未经 QingLong workload 证明,却扩大多架构 native 风险,拒绝。
7. **继续支持 Node 18/20 cluster runtime**:与 QL3 Node 24 能力、上游生命周期和新镜像目标冲突,拒绝。
## 验证要求
- package manager lock 中上述版本完全固定且没有 prerelease
- edge/standalone 与 cluster-control 使用不同 importer/产物审计;
- Node 24.18.0 下 x64/arm64 的 CJS build、migration 和 runtime smoke test 通过;
- PostgreSQL 16 与 18 均通过空库 migration、重放、checksum drift、leader competition 和 rollback
- runtime role 创建 schema/table/index 必须失败,但 history/capability audit 成功;
- migration role 不作为常驻 runtime Secret
- `pg-native` 在依赖图和产物中缺席;
- Drizzle 生成 SQL 只有经人工审查、canonical checksum 固定后才能进入 `pg-*` stream
- edge import/RSS/安装体积门禁和 cluster 多 Pool/failover 门禁同时通过。