从 #4410 / #4465 的收尾核查中发现,而且是那个 PR 自己加重的。
DatasourceSchema.readReplicas 的 JSDoc 写着:
Optional list of duplicate configurations for read-only operations.
Useful for scaling read throughput.
没有任何东西读它。
证据
ConnectableDatasource(service-datasource/src/datasource-connection-service.ts)没有
readReplicas 字段——数据源记录到连接规格的转换 toSpec() 里也没有它。
DatasourceConnectionSpec(contracts/datasource-driver-factory.ts)同样没有。
createDefaultDatasourceDriverFactory 只构建一个驱动,没有任何副本连接的构建点。
- 全仓 grep
readReplicas,packages/spec 的 schema 与其 dist 之外零命中。
也就是说:一个作者写了 readReplicas: [{ host: 'replica-1.internal', … }] 的数据源,读流量
全部照旧打到主库,没有任何提示。
为什么现在必须处置(这是 #4465 欠下的)
#4465 给 datasource.config 装门禁时,顺手把 readReplicas 的每个条目也按驱动契约校验了
——理由是"它承载同样的 per-driver config 形状",这话本身没错。但结果是:平台现在会认真
校验一个它根本不读的槽。
作者写错副本的 hostname 会收到一条精确的、可修的错误信息;写对了则什么也不会发生。这比
#4410 修掉的沉默更糟一层:沉默至少不假装在工作,而一个只在你写错时才出声的门禁,会让人确信
写对了就生效了。ADR-0078 no-silently-inert-metadata 的反面形态。
处置(二选一)
- remove —— 倾向这个。读副本路由是一个没人建过的功能,不是一个没接上的字段:它要
查询路由(哪些读走副本)、副本健康与故障回退、以及 ADR-0015 下与 schemaMode /
federation 的交互。schema 里一行 z.array(z.record(...)) 不是"差一点就好了",它只是描述了
一个不存在的东西。按 spec-property-retirement 走退役流程(tombstone + changeset 迁移
说明 + 账本),并把"我们想要读副本"另开 feature request。
- enforce —— 真去实现副本池 + 读路由。这是一个功能,不是一次收口,应当有自己的 ADR 与
验收,而不是塞进一个 enforce-or-remove 的收尾里。
无论走哪条,#4465 加的那段 readReplicas 校验都要一起处理:remove 就随字段删掉;enforce
就保留,但那时它才第一次名副其实。
参考
从 #4410 / #4465 的收尾核查中发现,而且是那个 PR 自己加重的。
DatasourceSchema.readReplicas的 JSDoc 写着:没有任何东西读它。
证据
ConnectableDatasource(service-datasource/src/datasource-connection-service.ts)没有readReplicas字段——数据源记录到连接规格的转换toSpec()里也没有它。DatasourceConnectionSpec(contracts/datasource-driver-factory.ts)同样没有。createDefaultDatasourceDriverFactory只构建一个驱动,没有任何副本连接的构建点。readReplicas,packages/spec的 schema 与其dist之外零命中。也就是说:一个作者写了
readReplicas: [{ host: 'replica-1.internal', … }]的数据源,读流量全部照旧打到主库,没有任何提示。
为什么现在必须处置(这是 #4465 欠下的)
#4465 给
datasource.config装门禁时,顺手把readReplicas的每个条目也按驱动契约校验了——理由是"它承载同样的 per-driver config 形状",这话本身没错。但结果是:平台现在会认真
校验一个它根本不读的槽。
作者写错副本的
hostname会收到一条精确的、可修的错误信息;写对了则什么也不会发生。这比#4410 修掉的沉默更糟一层:沉默至少不假装在工作,而一个只在你写错时才出声的门禁,会让人确信
写对了就生效了。ADR-0078 no-silently-inert-metadata 的反面形态。
处置(二选一)
查询路由(哪些读走副本)、副本健康与故障回退、以及 ADR-0015 下与
schemaMode/federation 的交互。schema 里一行
z.array(z.record(...))不是"差一点就好了",它只是描述了一个不存在的东西。按
spec-property-retirement走退役流程(tombstone + changeset 迁移说明 + 账本),并把"我们想要读副本"另开 feature request。
验收,而不是塞进一个 enforce-or-remove 的收尾里。
无论走哪条,#4465 加的那段
readReplicas校验都要一起处理:remove 就随字段删掉;enforce就保留,但那时它才第一次名副其实。
参考
datasource.config的驱动契约校验,本 issue 的来源).claude/skills/spec-property-retirement