Snorkel AI 的 CI/CD 流水线为每一个开放的拉取请求(PR)都配置了独立的 Lakebase Postgres 分支。这个分支是生产数据的完整读写副本,可在不到一分钟内准备就绪,而以往完整恢复一份副本往往需要 30 分钟甚至更久,这也正是他们此前始终无法为每个 PR 启用副本的根本原因。Lakebase 分支采用写时复制(copy-on-write)机制,因此在约 2.5 TB 的生产数据集上,即便同时存在 100 个 PR 数据库,新增存储也仅限于每个分支实际修改的数据。分支过期(TTL)机制会在对应 PR 结束后自动清理分支,使存储规模基本维持在生产数据本身加上活跃 PR 变更的水平。私有网络连接与 Okta SSO 支持,则共同提供了受治理且安全的环境。
问题的核心在于:面向生产数据的测试不该耗时 30 分钟以上。Snorkel AI 为 AI 团队构建数据开发工具,其用户包括专家级数据标注人员和前沿模型提供商。在该用例中,Snorkel 通过基于 Kubernetes 构建的 CI 流水线发布代码。每个开放的拉取请求以及临时提交都会获得各自的临时 Kubernetes 命名空间(即“PR 命名空间”)。开发者将分支部署到该命名空间,并在合并前运行完整测试套件。这些测试只有在针对真实数据运行时才最有价值。Snorkel 的生产数据库是一个 Amazon Aurora PostgreSQL 集群,而为每个 PR 命名空间提供该数据库的独立副本正是难点所在。此前恢复一份副本每个环境约需 20 分钟,在开发者全天不断提交 PR、合并队列每次运行都需要全新环境的场景下,这样的速度实在太慢。其他替代方案——共享预发布数据库、裁剪过的测试夹具或合成数据——又会重新带来生产级测试本应解决的问题:测试之间相互干扰、模式过期,以及只有真实数据形态才会暴露的缺陷。
Snorkel 为 Lakebase 在 CI/CD 场景中的成功设定了明确的验收标准。快速供给方面,新 PR 分支需在 60 秒内就绪(p95);规模方面,峰值时约需 100 个并发 PR 分支,每个分支都能独立读写;兼容性方面,现有应用与 CI 套件需通过标准 Postgres 在 Lakebase 上运行,性能与 Aurora 相当,并支持大型 JSONB/LOB 列;数据新鲜度方面,主分支需通过持续的 DMS 复制保持与生产 Aurora 相差约 2 分钟以内;安全性方面,生产数据须留在私有网络内、绝不经过公共互联网,开发者使用 Okta SSO 登录,CI 流水线则以服务主体身份进行认证。
数据库分支之所以契合 CI/CD,与 Lakebase 的架构特性直接相关。Lakebase 是 Databricks 的无服务器 Postgres 数据库,其存储与计算相互分离,并支持分支功能:可以像创建 Git 分支一样,从任意时间点创建一个新的、隔离的、可写的数据库分支。分支采用写时复制,创建分支只是一次元数据操作,创建时不会复制任何数据。新分支与父分支共享存储页,只有该分支修改过的页才会被写入为新的存储。对于 CI 而言,这意味着创建分支只需数秒而非数分钟,且与数据库大小无关;存储增长取决于每个分支产生的变更量,而非数据库规模,Snorkel 的每个 PR 分支在约 1 TB 基础数据之上大约写入 5 至 10 GB 的变更;计算资源按分支分配并可缩容至零,每个分支拥有独立的自动伸缩计算资源,在 CI 运行间隙会缩容至零,即便配置了 100 个分支,Snorkel 预计同时承载流量的也只有约 20 至 30 个,每个分支峰值时约每秒 5 次查询。
整体架构分为四步。第一步,在主分支上保持一份新鲜的生产副本。Lakebase 是标准 Postgres,因此可以直接作为 AWS DMS 的目标,无需任何定制工具。Snorkel 将一个 DMS 任务指向其生产 Aurora 数据库,并将其复制到 Lakebase 项目的主分支:先进行全量加载,再通过持续的变更数据捕获使主分支与生产数据的延迟保持在约 2 分钟以内。全 LOB 模式可完整搬运 10 至 20 MB 的 JSONB 值。由于团队本就在运行 DMS,因此无需构建或维护新的复制流水线。主分支即每个 PR 分支的“黄金副本”。第二步,在 PR 打开时创建分支。开发者提交 PR 时,CI 流水线调用 Lakebase API 从主分支创建分支,为其设置过期时间,并将分支的连接信息传入 PR 命名空间,分支在不到一分钟内即可就绪。第三步,针对真实数据运行测试。PR 命名空间中的应用通过标准 Postgres 连接到其对应分支,测试可以运行迁移、写入数据、修改模式,而不会影响生产、主分支或任何其他 PR。第四步,在 PR 关闭或测试通过后删除分支,并以 TTL 作为兜底。PR 合并或关闭时,流水线会删除该分支;若清理步骤未能执行,分支会在 TTL 到期后自行过期。
安全方面有两项要求直接排除了其他方案,因此值得单独说明。其一是私有连接:生产数据从 Aurora 迁移到 Lakebase、再从 Lakebase 传到 CI 集群,全程通过私有网络而非公共互联网。其二是身份认证:Snorkel 有两类数据库用户,一类是交互式连接以调试 PR 环境的开发者,另一类是在无人工介入下运行的 CI 流水线。由于 Okta SSO 围绕交互式浏览器登录设计,团队按用户类型拆分设计:开发者方面,Okta 联邦至 Databricks 账户,用户通过 SCIM 配置,开发者使用其惯常的 Okta 身份登录;CI 流水线方面,每个 PR 命名空间以 Databricks 服务主体(按团队或按仓库限定范围)运行,同样通过 SCIM 配置,PR Pod 使用服务主体凭据,通过 Databricks SDK 获取短期的 Lakebase OAuth 令牌,并在长时间测试运行期间刷新令牌,CI 中不存在长期有效的数据库密码。审计方面,Postgres 审计日志会记录每次查询对应的服务主体身份,因此每条查询都可追溯到执行它的流水线。
写时复制与分支过期这两项特性共同保证了 CI 在大规模下既快又省。写时复制让分支即时可用:新分支不复制任何数据,而是指向父分支已有的存储页,只有被修改的页才会写入新存储;从约 1 TB 的生产副本创建分支与从 1 GB 数据库创建分支耗时相当,都是数秒,而非此前完整恢复所需的约 20 分钟,每个 PR 命名空间都能立刻获得一份隔离、可写的生产数据副本。写时复制也降低了分支成本:每个分支与主分支共享绝大部分数据,存储随每个分支产生的变更增长,而非随数据库规模增长,对 Snorkel 而言,即每个 PR 分支在共享基础之上约增加 5 至 10 GB;计算按分支分配并在空闲时缩容至零,因此只有正在运行测试的分支才会占用计算资源。分支过期则让长期成本保持低廉:写时复制在分支生命周期较短时收益最大,一个分支会保留其创建时的数据版本,随着 DMS 持续向主分支写入生产变更,长期存在的分支会一直持有那些较早的页,其占用的存储也会随时间增长;为每个 CI 分支设置 TTL,意味着每个分支在 PR 完成后会被自动移除,从而使存储规模接近生产基础数据加上活跃 PR 变更的水平。
同样的模式也适用于 AI 智能体。需要生产数据一次性副本的工作负载不止 PR 命名空间,编写 SQL、执行迁移或修改数据的 AI 智能体同样需要一个安全的试验场所,而写时复制分支正好提供了这样的环境。可以为每个智能体任务创建一条主分支的副本:智能体可以读取真实数据、写入数据,甚至修改模式,而不会触及生产或任何其他智能体的工作;无论数据库多大,创建分支都只需数秒,因此为每个任务创建分支是切实可行的。多个智能体可以并行运行:存储只随每个分支修改的页增长,每个分支的计算在空闲时缩容至零,因此并排运行数十个智能体的成本大致相当于它们所做的变更,而非数十份数据库副本。团队可以审查智能体所做的更改,或直接丢弃:在将任何内容应用到生产之前,可以检查智能体在其分支上改了什么;如果结果不对,删除分支即可,无需回滚任何内容。TTL 可以替智能体完成清理:智能体可能产生大量短生命周期分支,为每个分支设置过期时间可自动移除被放弃的分支,与 CI 分支的清理方式相同。智能体也可获得限定范围的身份:与 CI 流水线一样,智能体可以以服务主体身份配合短期 OAuth 令牌运行,因此智能体执行的每条查询都能在审计日志中被追踪。
在结果方面,供给时间从此前的 30 分钟以上降至使用 Lakebase 后的 60 秒以内;规模上按约 2.5 TB 生产数据集、约 100 个并发 PR 分支进行设计;兼容性上,CI 套件在 Lakebase 上运行“没有重大兼容性问题”,性能与 Aurora 相当;采用范围已推广至各团队共 60 至 70 名开发者。一位开发者表示:“以前我们必须物理复制数据,这些复制作业耗时数小时,并且经常在各类 CI 平台(GitHub Actions、CircleCI 等)上遇到网络及其他瓶颈。我们一直在寻找一种解决方案,能在数据离开我们的 VPC 时仍满足我们的安全与合规要求。”
面向 Lakebase 分支在 CI/CD 中的最佳实践包括:每个 PR 一条分支,且从持续同步的主分支创建;始终设置 TTL,将其视为必需项而非可选项,并与 PR 或测试运行的通常时长相匹配;需要更新数据时创建新分支而非保留旧分支,从主分支重新创建只需数秒;让计算缩容至零,因为任一时刻大多数已配置分支都处于空闲状态;流水线使用服务主体与短期 OAuth 令牌,人员使用 SSO;按分支监控存储,以便及早发现长期存在的分支。对于希望让每个拉取请求都拥有类生产数据库的团队,可以从 Lakebase 入手,并阅读分支文档,在 CI 流水线中配置分支过期。