多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Agent Substrate 的 PostgreSQL 模式演进:滚动更新下的迁移契约、Expand-and-Contract 与分区友好约束

Agent Substrate 的 PostgreSQL 模式演进:滚动更新下的迁移契约、Expand-and-Contract 与分区友好约束 人工智能AI AgentAgent 沙箱云原生容器运行时零信任【免费下载链接】substrateAgent Substrate: the core system项目地址https://gitcode.com/GitHub_Trending/substrate7/substrate点击查看免费下载本篇指南面向在 Agent Substrateateapi中维护 PostgreSQL 存储层的开发者系统讲解该项目在滚动更新场景下安全演进数据库模式Schema的完整工程约束先说明迁移必须跑在就绪之前、逐条提交、失败可续跑的运行模型再给出兼容性契约、Schema 变更规则、actors 表可分区性红线、Expand-and-Contract 三步法与迁移文件规范最后以仓库内的真实迁移文件、校验脚本与测试用例逐一佐证。读完你将能独立编写符合项目规范的迁移文件并在提交前完成完整的自查与验证。为什么模式演进是滚动更新里最危险的一环在 Agent Substrate 中ateapi 是控制面 API 服务它把 Agent 的元数据持久化到 PostgreSQL。其核心运行模型在文档 docs/dev/postgresql-schema-evolution.md 中写得很清楚ateapi在变为 ready就绪之前应用 PostgreSQL 迁移连接建立后会先执行迁移迁移成功后才对外提供服务。在 atepg.go 的Connect中顺序是打开连接池 →Ping→createSchema→newPersistence内部调用applyMigrations随后主程序才通过 serverboot.Readiness 标记就绪。滚动更新期间旧二进制继续服务Kubernetes 滚动发布会让新旧两个版本的ateapi同时存活新二进制改 Schema旧二进制同时读写同一套表。Goose 一次只提交一个迁移迁移按文件逐个在事务中应用任何一个迁移失败它之前已完成的迁移前缀会保留在库中。由此得出的铁律是每一个迁移前缀migration prefix都必须让旧二进制安全读写。这正是下文所有规则的设计原点。从源码看迁移引擎选用 pressly/goose/v3schema.go通过goose.NewProvider(goose.DialectPostgres, ...)创建 Provider迁移文件用//go:embed migrations/*.sql打进二进制账本表固定命名为schema_migrationsgoose.WithTableName(schema_migrations)。兼容性契约六条不可逾越的底线文档为每次 Schema 变更定义了六条兼容性契约全部围绕前一个版本的二进制在任意迁移前缀下都能工作展开每个迁移前缀都要兼容旧二进制的读写——即任意时刻停住发布旧进程都不能坏。把每个迁移文件边界视为一个持久化的数据库状态——迁移是逐条落地的不是一批原子操作。不得依赖后续迁移来修复前面前缀的兼容性——前缀本身必须自洽。让新二进制从上一个发布版本的状态出发执行迁移——升级路径是线性的 N-1 → N。新二进制在全部待处理迁移完成之前不得就绪——这就把迁移失败转化为服务不 ready而不是带病上线。在SELECT和INSERT中显式列出列名——显式列清单让语句在新列加入后依然语义稳定。第 6 条在存储层代码中有直接印证例如 actor.go 的写入是INSERT INTO actors (atespace, name, uid, version, proto) ...明确枚举列名而非INSERT INTO actors VALUES ...这样旧二进制写入时即便新二进制已加了列也不会因为列数错位而失败。Schema 变更规则偏好在加法上做文章优先做增量additive变更在新二进制使用新结构之前先把结构加上是基本顺序新增表、列、索引要先于依赖它的新代码上线。新增列必须可空nullable或带兼容的数据库默认值否则旧二进制的新增INSERT会因为漏掉该列而失败。这条规则在 actor.go 这类显式列清单的INSERT语句下体现得尤为直接新列若NOT NULL且无默认值旧二进制立刻被卡死。存储层采用了原生列 完整 protobuf 字节的混合模型见 atepg.go 包注释每张表既有 SQL 需要操作的原生列也有BYTEA存放完整消息。读取时用proto.UnmarshalOptions{DiscardUnknown: true}丢弃当前二进制没有描述的字段并回填默认值unmarshalStored这保证了新副本写入的新字段旧副本读到也不会崩——这是数据兼容的又一道防线。严禁做的事不得删除或重命名旧二进制还在用的表/列不得以不兼容方式改变其类型或语义。不得在旧二进制可能写入违反约束的值时收紧约束不得删除旧二进制依赖的默认值。两种结构并存、两个二进制都能写时必须让两者保持一致在新二进制要求新结构之前先完成存量行的回填backfill。启动阶段不得执行大规模数据回填——这类工作必须另立独立的迁移流程并先提出方案评审而不是塞进启动迁移里。这些规则的精神可以概括为一句话Schema 是共享资源变更必须对仍在运行的所有版本负责而不是只对新版本负责。保持 actors 表可分区一条未来选项红线项目为未来预留了把actors表按atespace或按name分区、以及其他带atespace列的表按atespace分区的可能性。为此任何 Schema 变更或查询都不得引入以下三类结构唯一索引/唯一约束遗漏分区键在带atespace的表上建唯一索引/约束必须包含atespace在actors上必须包含name。例如在 000001_initial.sql 中actors的主键就是(atespace, name)天然满足该要求。外键引用遗漏分区键引用这些表的列集合必须包含atespace引用actors必须用(atespace, name)。初始迁移中actor_egress_policies的FOREIGN KEY (atespace, actor_name) REFERENCES actors(atespace, name)正是这个形态。查询不带分区键过滤对这些表的查询必须过滤atespace对actors的查询还必须过滤name。按定义结果横跨所有分区的语句如全局列表必须登记在TestActorsTablePartitionable中其他会读到多个分区的语句需要在该测试中获得社区同意的豁免。这条红线在测试侧由 partition_test.go 的TestActorsTablePartitionable强制它把一份全新迁移后的 Schema 实际按atespace对actors再按name改造成哈希分区9 个分区见partitions常量然后在这套分区布局上跑完整的 store 契约套件storecontract.RunContractTests并用 pgx 的QueryTracer对每条语句做EXPLAIN一旦发现某个语句的计划读了多个分区就报错。测试还显式验证了两类反例CREATE UNIQUE INDEX actors_uid_key ON actors (uid)这种遗漏分区键的唯一索引会被 PostgreSQL 拒绝WHERE uid $1与WHERE atespace ANY($1)这类不带/多值分区键的谓词会触发扇出告警。Expand and Contract替换或删除结构的标准三步法当需要替换或删除某个结构时采用先扩后缩的三步发布序列Release N新增新结构同时保留旧结构新二进制开始写新结构旧二进制继续用旧结构。Release N1停止使用旧结构所有读写都切到新结构旧结构变成死数据。Release N2删除旧结构。该序列保证发布过程中任意时刻前一个版本的二进制都能兼容当前 Schema即便二进制临时回滚binary rollback由于二进制回滚不会回滚 Schema前一个版本仍能正常运行——这正是为什么不能依赖回滚迁移来兜底而必须用 expand-and-contract 让每个前缀自洽。迁移文件规范目录、命名与内容红线迁移文件统一存放在cmd/ateapi/internal/store/atepg/migrations。文档规定的硬性规则如下使用下一个顺序编号的NNNNNN_name.sql文件名6 位数字前缀 小写下划线命名。恰好包含一条-- goose Up注解。只使用 SQL 迁移。不添加 down 迁移Goose 的-- goose Down被明确禁止。不使用NO TRANSACTION每个迁移必须在事务中执行或ENVSUB。不添加 SQL 事务控制语句BEGIN/START TRANSACTION/COMMIT/ROLLBACK事务交给 Goose 控制。不把IF NOT EXISTS用作 Schema 变更的保护伞失败必须显式暴露而不是悄悄跳过。每个启动迁移保持简短。真实范例000001_initial.sql仓库当前唯一一份迁移文件 000001_initial.sql 就是完全合规的样板文件以-- goose Up开头随后依次创建atespaces、actors主键(atespace, name)、actor_egress_policies、actor_templates、tags、workers、worker_assignments、按created_at范围分区的worker_outbox含UNLOGGED的 DEFAULT 分区与worker_outbox_trim高水位表、leases等表。注意其中注释说明了worker_outbox的created_at必须用clock_timestamp()而非now()后者在事务开始时冻结会让慢事务写入已过期的分区——这类在迁移文件里保留设计动机的注释正是后续维护者判断能不能动这行 DDL的关键上下文。版本边界v1 之前可改v1 之后只增不改首个稳定 v1 发布之前允许修改或合并squash迁移文件迁移历史一旦变化需要重建开发数据库DROP SCHEMA ... CASCADE后重跑即可测试代码正是这样做的。首个稳定 v1 发布之后已发布的迁移文件不得修改或删除出错只能通过新增迁移来纠正。不要手工编辑schema_migrations账本表。校验脚本把规则变成 CI 门槛hack/verify/postgresql-migrations.sh 将上述规则脚本化逐一检查每个迁移文件文件名必须匹配^([0-9]{6})_[a-z0-9_]\.sql$且版本号必须严格等于期望的下一个顺序号expected从 1 递增必须包含-- goose Up不得包含-- goose Down、-- goose no transaction、IF NOT EXISTS、-- goose ENVSUB不得出现行首的BEGIN/START TRANSACTION/COMMIT/ROLLBACK可选参数released-ref用于指定已发布 tag脚本会检查自该 ref 以来迁移目录是否存在修改-diff-filterMD的改动或删除存在则报错 Do not change or delete released PostgreSQL migrations.。不传参数时脚本会自动从git tag --merged HEAD中挑出最新的vMAJOR.MINOR.PATCH语义化版本 tag 作为 released-ref。提交前自查清单与验证命令文档给出了提交前必须完成的五步找出旧二进制会读写所变更对象的全部操作逐一核对每个新迁移前缀下这些操作是否仍成立某个前缀会破坏操作时改用 expand-and-contract为 Schema 行为新增或更新测试运行迁移校验脚本和 PostgreSQL 存储测试hack/verify/postgresql-migrations.sh go test ./cmd/ateapi/internal/store/atepg源码佐证迁移如何安全落地最后看迁移执行层 schema.go 与配套测试 migrations_test.go理解文档规则背后的实现保障版本门槛applyMigrations先读server_version_num要求 PostgreSQL 13xid8、pg_current_xact_id等特性依赖版本不足时给出明确报错而非晦涩的 DDL 失败。遗留库防护rejectUnversionedSubstrateSchema在 Goose 创建账本表之前检查存在 Substrate 表但没有schema_migrations账本的库直接拒绝防止把迁移前的旧 schema 误标为已托管。对应测试TestMigrationSchemaStates/tables without metadata验证了该场景不会创建账本。并发安全多个副本同时启动时通过基于hashtextextended计算锁 ID 的 PostgreSQL advisory session locklock.NewPostgresSessionLocker超时 1 秒 x 300 次串行化迁移。TestMigrationsConcurrentStartup同时起两个Connect断言每个迁移恰好应用一次TestMigrationsWaitForInProgressMigration则验证迁移会阻塞等待他人持有的锁。失败可续跑migrateToLatest把 goose 的PartialError展开记录已应用前缀后返回错误TestMigrationFailureLeavesCompletedPrefixAndResumes构造成功→成功→失败三个迁移断言版本 2 保留、失败迁移的事务整体回滚、修正版本 3 后可从版本 2 续跑成功——这正是文档失败的迁移运行会保留已完成前缀的落地证明。就绪语义迁移在主服务标记 ready 之前完成main.go 的就绪管理与 atepg.go 的Connect顺序迁移失败即启动失败不会带病对外服务。综上Agent Substrate 的 PostgreSQL 模式演进不是一套口号而是文档契约 脚本校验 测试强制的三重闭环文档定义为什么postgresql-migrations.sh 检查怎么写migrations_test.go 与 partition_test.go 验证跑起来是否安全。任何新增迁移都应在这套闭环内完成设计与提交。赞分享人工智能AI AgentAgent 沙箱云原生容器运行时零信任【免费下载链接】substrateAgent Substrate: the core system项目地址https://gitcode.com/GitHub_Trending/substrate7/substrate点击查看免费下载相关推荐如何用免费开源眼动追踪工具实现视线控制电脑如何用免费开源眼动追踪工具实现视线控制电脑 想象一下仅凭眼睛就能控制电脑光标、点击链接、输入文字——这不再是科幻电影的场景而是开源项目eyetracker带数据库破坏性变更治理Unleash 的 Expand/Contract 迁移模式与零停机演进实践数据库破坏性变更治理Unleash 的 Expand/Contract 迁移模式与零停机演进实践 本篇技术指南围绕 Unleash 后端架构决策记录ADR后端RxJS 7 到 RxJS Next 迁移的评估、基线与目标契约assessment-and-contract 实战指南RxJS 7 到 RxJS Next 迁移的评估、基线与目标契约assessment and contract 实战指南 RxJS Next 迁移不是一次简单前端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表