39fcf2631d
Postgres requires a partitioned table's unique constraints to cover the partition key, so ranging on created_at forces the primary key to (call_id, created_at) -- and ON CONFLICT (call_id) DO NOTHING then matches no constraint at all. The retention design claimed INSERT stays transparent under partitioning; that holds for the routing, not for the conflict target, and telemetry would have failed outright on any partitioned deployment. The write drops its conflict target, which is byte-equivalent on a plain table and legal on both. The cap design gains the three emitter construction sites it has to touch and the relationship to the 200-char caps embed and OCR already carry: they stay, and the new cap is the stricter of the two. Covering all three call paths is deliberate -- their rows land in one table, and issue #11 settled that argument already.
3.9 KiB
3.9 KiB
type, node_id, title, date
| type | node_id | title | date |
|---|---|---|---|
| design | design:issue13-schema-mode | issue #13: 遥测 schema 自动 ALTER 降级为按后端不对称的显式档位 | 2026-08-19 |
issue #13: 遥测 schema 自动 ALTER 降级为按后端不对称的显式档位
正文: 2026-08-19-issue13-schema-mode-design.md。状态: 待人类审批。同批交付 design:issue12-telemetry-retention。
- 选定方案: 新增
PGW_TELEMETRY_SCHEMA_MODE=auto|manual(三态,未设时按后端派生: SQLite→auto、Postgres→manual)。manual 档探测真实列集合后不发 DDL,改为 warning 逐列点名 + 打印可执行 SQL,并按现有列裁剪 INSERT 继续写入。新增公共函数telemetry_schema_sql(backend)供下游主动索取建表/补列脚本。 - 为什么两侧不对称: issue 引用的全部先例(Hangfire 锁队列雪崩、Prefect 多实例竞态、Alembic 审计链)语境都是共享的生产 PG——
ALTER TABLE ADD COLUMN取 ACCESS EXCLUSIVE 锁,排在长事务后会阻塞该表其后所有查询,而遥测是业务路径上的内联 await。SQLite 侧则是下游自己的本地文件(VT/CHSAnalyzer/dissect 的runs/*.db全是这个形态): 无 DBA、无迁移工具、无第二个系统碰它。强加手工 SQL 是净损失。两侧有意不对称在本库已有先例(issue #9 的建表探测)。 - 关掉 ALTER 必须配套裁剪写入: 今天
_INSERT是 24 列固定语句,旧表缺列时若不 ALTER 则 INSERT 全部失败 → 逐行 warning → 遥测彻底丢失,比自动 ALTER 更严重地违反"遥测必录"。降级写入不是增强,是本变更成立的前提。 - 打印的 SQL 必须与执行的 DDL 同源:
_DDL/_BACKFILL/_COLUMNS今天在两个 recorder 各存一份,公共函数再写一份则三份必然漂移,表现为"下游照打印的 SQL 建完表,库仍报缺列"。故收敛进新的telemetry/schema.py作单一事实源——这是正确性要求,不是顺手重构。 - manual 档不停
CREATE TABLE: issue 把建表列为现状描述而非指控(已在 #3/#9 收口为先探测后建);新建表无既有数据、无并发访问者,不存在锁与数据风险,停掉它会断掉零配置起步。Celery 先例同样是"自动建表 + 永不 ALTER"。 - 缺省规则落 config 层(人类决策): recorder 的
auto_migrate为 keyword-only 必填,派生只写在 config 一处,不与类签名漂移。代价是 35 处直接构造点需改。 - 被否决备选: 两侧统一默认 manual(现有 SQLite 下游升级即需人工干预,而这些场景没有承接手工 SQL 的角色);保持 auto 默认只加开关(默认状态仍是库在下游生产表发不受控 DDL,核心诉求未满足);Celery 式无开关永不 ALTER(SQLite 净损失且下游无出路);APScheduler 4.x 式"schema 不认识就拒绝启动"——与"遥测初始化失败必须静默降级、不得拖垮业务调用"的库铁律正面冲突,不可选。
- 附带成文化: Expand/Contract 纪律(新列只增不删不改名、必可空或带非易失默认、INSERT 显式列名、库从不
SELECT *)升格为文档化承诺。它是 design:issue12-telemetry-retention 分区方案能成立的前提——下游把表建成分区表后,库的to_regclass探测与 INSERT 路由才对分区透明。 - 审查留痕(Codex,2026-08-19): 报 3 项。采纳 1 项(阻断级)——PG 的
ON CONFLICT (call_id) DO NOTHING与 issue #12 的分区方案不兼容: PostgreSQL 要求分区表的唯一约束必须包含分区键,按created_at分区后主键被逼成(call_id, created_at),该语句再也匹配不到约束,遥测在分区部署下全线写不进去。改为无冲突目标的ON CONFLICT DO NOTHING(两种表形态都合法,普通表上逐字等价),改动归本 issue(它已在重写 INSERT 构造逻辑),见正文 §4.6。原设计"INSERT 路由对分区表透明"的判断只对普通 INSERT 成立,对冲突目标不成立——这是"透明"二字被推得过宽的典型。