如何实现数据库每日部署

Hacker News Top 产品

摘要

Turbopuffer 描述了他们利用 Kubernetes CRDs 和控制平面实现数据库每日升级部署的方法,该控制平面支持公共 SaaS、单租户 SaaS 和 BYOC 部署,且无需直接访问。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/08/17 15:57

# 如何实现数据库的每日迭代部署 来源:https://turbopuffer.com/blog/control-plane 2026年8月14日 • Tarun Pothulapati(工程师) 每天,turbopuffer 客户都会提出各种需求:新的查询方案、新的 API、新的索引结构。为此,我们每天要在集群中进行数十次数据库升级,其中许多都在提交 PR 的同一天完成部署。快速迭代让每位客户都感受到专属服务般的体验(https://turbopuffer.com/customers/linear)。 我们希望不限制 turbopuffer 的部署环境,因此支持三种部署模式下的多区域部署(https://turbopuffer.com/docs/regions):公有云 SaaS、单租户 SaaS 和 BYOC(自带云部署)。目前我们运营着超过 100 个集群,数量是六个月前的两倍,并且随着公有云区域扩展和更多 BYOC 部署,这个数字还在持续增长。 ``` ╔═ turbopuffer 云账户 ═════════════════════╗ ╔═ 客户云账户 ═══╗ ║ ║░ ║ ║░ ║ ┏━ 公有云 ━━━━━━━━━┓ ┏━ 单租户 ━━━━━━━┓ ║░ ║ ┏━ BYOC ━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ AWS | GCP ┃ ┃ AWS | GCP ┃ ║░ ║ ┃ AWS | GCP | Azure ┃ ║░ ║ ┃ 共享资源 ┃ ┃ 专用资源 ┃ ║░ ║ ┃ 客户自有资源 ┃ ║░ ║ ┃ ┃ ┃ ┃ ║░ ║ ┃ ┃ ║░ ║ ┃ tpuf 运维权限 ┃ ┃ tpuf 运维权限 ┃ ║░ ║ ┃ 无 tpuf 运维权限 ┃ ║░ ║ ┗━━━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━┛ ║░ ║ ┗━━━━━━━━━━━━━━━━━━━━━━┛ ║░ ║ ║░ ║ ║░ ╚══════════════════════════════════════════╝░ ╚════════════════════════════╝░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ``` ``` ╔═ tpuf 账户 ═════════════╗ ║ ┏ 公有云 ━━━━━━━━━━━━━━┓ ║ ║ ┃ AWS | GCP ┃ ║ ║ ┃ 共享资源 ┃ ║ ║ ┃ tpuf 运维权限 ┃ ║ ║ ┗━━━━━━━━━━━━━━━━━━━━━━┛ ║ ║ ┏ 单租户 ━━━━━━━━━━━━━┓ ║ ║ ┃ AWS | GCP ┃ ║ ║ ┃ 专用资源 ┃ ║ ║ ┃ tpuf 运维权限 ┃ ║ ║ ┗━━━━━━━━━━━━━━━━━━━━━━┛ ║ ╚══════════════════════════╝ ╔═ 客户账户 ═════════════╗ ║ ┏ BYOC ━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ AWS|GCP|Azure ┃ ║░ ║ ┃ 客户自有资源 ┃ ║░ ║ ┃ 无 tpuf 运维权限 ┃ ║░ ║ ┗━━━━━━━━━━━━━━━━━━━━━━┛ ║░ ╚══════════════════════════╝░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ``` ## BYOC 的挑战 BYOC 集群部署在客户的云账户中,我们默认不持有访问凭证。无法直接 SSH 或执行 `kubectl` 命令。部分 BYOC 服务商采用的做法是让客户划分专用云账户并授予永久管理员权限,这虽然能隔离客户其他云资源,但每个专用账户都会带来安全监控、合规审计和账单管理的额外负担,我们希望避免这种情况。 我们不希望为 BYOC 和 SaaS 使用不同的控制平面,因此必须基于最通用的方案设计。必须能够*无需直接访问*即可管理*每个*集群。 ## 如何运维无法直接访问的数据库集群? 实现这一目标的唯一方式是:集群需要在*断开与中心控制平面连接*的情况下,仍能独立驱动所有操作至最终状态。 解决方案基于标准 Kubernetes 架构。每个集群都运行一个本地集群代理,实现简单的状态机。一个名为 `TurbopufferOperation` 的 Kubernetes CRD(自定义资源定义)涵盖所有操作类型,从 `upgrade`(升级)到 `tidy`(清理)。Kubernetes 控制器通过调谐循环驱动每个 CR(自定义资源)在状态间流转,直至达到终止状态。操作默认会自动推进,但 BYOC 客户可以设置审批门控(https://turbopuffer.com/docs/byoc/control-plane#configuring-manual-approval-for-your-cluster)或维护窗口(https://turbopuffer.com/docs/byoc/control-plane#restricting-upgrades-to-a-maintenance-window)。这些机制被编码为 CRD 的等待状态,待审批通过或窗口期开始后自动推进。 ``` ╔═ 操作生命周期 ═══════════════════════════════════════════╗ ║ ║░ ║ ┏━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ 需审批状态 ┃ ║░ ║ ┗━━━━━━━━┯━━━━━━━━━━┛ ║░ ║ │ 审批通过(自动或手动) ║░ ║ ▼ ║░ ║ ┏━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ 待处理 ┃ ──▶ ┃ 等待维护窗口期 ┃ ║░ ║ ┗━━━━━━━━┯━━━━━━━━━━┛ ◀── ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ║░ ║ │ start() ║░ ║ ▼ ║░ ║ ┏━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ 运行中 ┃ ──▶ ┃ 等待外部执行 ┃ ║░ ║ ┗━━━━━━━━┯━━━━━━━━━━┛ ◀── ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ║░ ║ │ poll() ║░ ║ ┌────┴────┐ ║░ ║ ▼ ▼ ║░ ║ ┏━━━━━━━┓ ┏━━━━━━━┓ ║░ ║ ┃ 成功 ┃ ┃ 失败 ┃ ║░ ║ ┗━━━━━━━┛ ┗━━━━━━━┛ ║░ ║ ║░ ╚═══════════════════════════════════════════════════════════╝░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ``` ``` ╔═ 操作生命周期 ════════╗ ║ ┏━━━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ 需审批状态 ┃ ║░ ║ ┗━━━━━━━━━┯━━━━━━━━━━━┛ ║░ ║ ▼ 审批通过 ║░ ║ ┏━━━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ 待处理 ┃ ║░ ║ ┗━━━━━━━━━┯━━━━━━━━━━━┛ ║░ ║ ▼ start() ║░ ║ ┏━━━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ 运行中 ┃ ║░ ║ ┗━━━┯━━━━━━━━━━━━━┯━━━┛ ║░ ║ ▼ poll() ▼ ║░ ║ ┏━━━━━━━━━┓ ┏━━━━━━━━━┓ ║░ ║ ┃ 成功 ┃ ┃ 失败 ┃ ║░ ║ ┗━━━━━━━━━┛ ┗━━━━━━━━━┛ ║░ ╚═════════════════════════╝░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ``` 关键在于定义足够通用的状态来建模所有操作类型(包括现有和未来的),同时状态集必须有限,以便调谐器能始终将其驱动至终止状态。我们无需直接操作即可完成工作,状态作为持久化对象存储在集群的 `etcd` 中。即使代理崩溃或失去与控制平面的连接,仍能将工作推进至完成状态。 这种本地持久化状态机意味着我们无需直接访问集群来*驱动*工作,但集群如何*获取*其工作内容呢? ## 你使用 Terraform,对吧? 常规做法会采用 Terraform、Helm 等基础设施即代码(IaC)工具。这些工具非常适合基础设施编排,我们也确实用它们来配置资源!但它们并不适合作为管理数据库集群的控制平面。 首先,turbopuffer 没有专门的 DevOps 团队。我们要求数据库工程师自行部署代码到基础设施。他们大多缺乏 IaC 工具使用经验,因此不适合将其置于关键路径上。 其次,Terraform 的默认模式需要我们从自身基础设施执行 `terraform apply`,以客户基础设施为目标,这对 BYOC 场景不可行。Terraform 的 GitOps 流程可以解决此问题:让客户在 Git 仓库更新时自动触发其云账户内的 `terraform apply`。理论上适用于数据库升级:只需在 Terraform 配置中更新 Docker 镜像版本。但谁管理仓库?如果由我方管理,需要审批门控的 BYOC 客户无法控制合并;如果由客户管理,我们又需要访问他们系统的权限,或依赖人工合并 PR。 无论如何,我们的许多操作并非纯声明式。例如临时命名空间重建索引、压缩 WAL 或 LSM 树垃圾回收,这些是需要执行的任务,而非要达成的状态。每个临时操作都需要通过 git 提交新的配置文件来创建任务,再通过另一次提交来清理已完成的任务。Terraform 文件适合作为声明式配置清单,但作为任务队列则非常糟糕。 ## 代理如何获取工作(以及我们如何跟踪状态) 我们无法直接访问集群,也不会使用 GitOps,但集群仍需从控制平面*拉取*工作并*推送*自身状态。系统必须完全容错:集群与控制平面之间的连接中断不应影响健康集群的运行。连接丢失期间,集群应在重新连接后获取新操作;控制平面则需能同步集群状态。 为实现这些目标,我们构建了自定义中心控制平面,包含 API 服务器和 PlanetScale MySQL 数据库。 每个集群代理被分配集群专属的 API 密钥。代理定期通过认证的 `GET` 请求轮询 API 服务器获取待处理操作。获取操作后,代理将其存储为自定义资源并通过调谐循环执行。这具有幂等性:每个操作的 CR 以其 ID 命名,若因故重复返回同一操作,代理会找到现有 CR 并从 `etcd` 中存储的状态恢复,而非创建副本。 随着操作推进,集群代理会定期将缓冲的状态变更 `POST` 到 API 服务器,服务器将其镜像到 MySQL 的 `status_transitions` 表中,作为追加写日志。 ``` tpuf 工程师 │ ▼ ╔═ 控制平面 ═══════╗ ╔═ 集群 ══════════════════╗ ║ ║░ ║ ║░ ║ ┏━━━━━━━━━━━━━━━┓ ║░ ║ ┏━ 集群代理 ━━━━━━━━┓ ║░ ║ ┃ UI ┃ ║░ ║ ┃┌────────────────────┐┃ ║░ ║ ┗━━━━━━━┯━━━━━━━┛ ║░ ║ ┃│ k8s 控制器 │┃ ║░ ║ ▼ ║░ ║ ┃│ (同步+调谐循环) │┃ ║░ ║ ┏━━━━━━━━━━━━━━━┓ ║◀─── GET 操作 ────║ ┃└────────┬─────────┘┃ ║░ ║ ┃ API 服务器 ┃ ║░ ║ ┃ ▼ ┃ ║░ ║ ┗━━━━━━━┯━━━━━━━┛ ║◀── POST 状态变更 ──║ ┃┌────────────────────┐┃ ║░ ║ ▼ ║░ ║ ┃│ TurbopufferOperation │┃ ║░ ║ ┏━━━━━━━━━━━━━━━┓ ║░ ║ ┃│ CRs │┃ ║░ ║ ┃ MySQL (PlanetScale)┃ ║░ ║ ┃└────────────────────┘┃ ║░ ║ ┗━━━━━━━━━━━━━━━┛ ║░ ║ ┗━━━━━━━━━━━━━━━━━━━━━━┛ ║░ ║ ║░ ║ ║░ ╚═══════════════════╝░ ╚══════════════════════════╝░ ░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ``` ``` tpuf 工程师 │ ▼ ╔═ 控制平面 ════════════╗ ║ ┏━━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ UI ┃ ║░ ║ ┗━━━━━━━━━┯━━━━━━━━━━┛ ║░ ║ ▼ ║░ ║ ┏━━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ API 服务器 ┃ ║░ ║ ┗━━━━━━━━━┯━━━━━━━━━━┛ ║░ ║ ▼ ║░ ║ ┏━━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ MySQL ┃ ║░ ║ ┃ (PlanetScale) ┃ ║░ ║ ┗━━━━━━━━━━━━━━━━━━━━┛ ║░ ╚════════════════════════╝░ ░░░░░▲░░░░░░░░░░░░░▲░░░░░░░░░░░ │GET 工作 │POST 状态 │ │ ╔═ 集群 ══════════════════╗ ║ ┏━ 集群代理 ━━━━━━━━━━━━┓ ║░ ║ ┃┌────────────────────┐┃ ║░ ║ ┃│ k8s 控制器 │┃ ║░ ║ ┃│ (同步+调谐循环) │┃ ║░ ║ ┃└────────┬─────────┘┃ ║░ ║ ┃ ▼ ┃ ║░ ║ ┃┌────────────────────┐┃ ║░ ║ ┃│ TurbopufferOperation │┃ ║░ ║ ┃│ CRs │┃ ║░ ║ ┃└────────────────────┘┃ ║░ ║ ┗━━━━━━━━━━━━━━━━━━━━━━┛ ║░ ╚══════════════════════════╝░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ``` 若代理的 `POST` 请求未收到响应,代理会将状态变更重新放入缓冲区并重试。即使响应未返回,`POST` 可能已成功写入,因此同一变更可能在日志中出现两次。这无关紧要:操作的当前状态仅由其最新状态变更决定。 这就是我们在连接临时中断时仍能保持中心控制平面与各集群同步的方式。数据库维护着所有集群预期操作的权威来源,并镜像这些操作的状态,使我们能远程重建集群状态。 但数据库接口具有危险性。我们说过不想用 YAML 部署代码——若用 `INSERT INTO` SQL 语句替代,那将更加不可取。 ## 我们真正喜欢使用的界面 我们每天多次部署,并在部署间隙调试维护集群。作为 turbopuffer 数据库工程师,你希望控制平面如同你最喜爱的代码编辑器般可靠易用。 控制平面拥有基于 Remix/React 的定制仪表盘,作为 API 服务器的前端。其用户体验受 Linear 启发:所有操作支持快捷键,可完全通过键盘操作。信息层级结构清晰,选择状态明确,让你始终清楚当前操作的集群。 升级是最常见的操作,UI 设计体现了这一点。我们可以快速查看每个集群已部署的提交 SHA 及其与最新通过版本的差异,并展示从 GitHub 拉取的元数据(提交信息、PR 编号、提交日期),无需手动解读 SHA。升级通常批量进行,我们可以跨所有部署模型选择多个集群,指定目标提交 SHA,并通过键盘完成批量升级。其他所有操作也可通过此界面启动。 你的浏览器不支持视频标签。0:00/0:00 选择集群,指定目标 SHA,通过键盘完成批量升级。 我们不要求工程师始终盯着仪表盘,因此每个操作的状态变更都会实时推送到专属 Slack 线程。若操作失败,会立即在频道中发出醒目告警,吸引团队关注和修复。这种方式让我们无需持续监控就能及时响应。 当 BYOC 集群操作需要审批时,Slack 集成会自动通过客户专属的支持频道通知客户进行审批。 01/02 升级线程展示提交差异、发布状态及相关讨论。 控制平面还为我们常用的调试和监控接口提供便捷入口。若需密切关注某个操作,可直接从控制平面跳转到 Datadog 的日志、跟踪或指标(已限定其集群范围)。当客户报告高延迟时,我们能快速在 Polar Signals 中调取其查询 Pod 的内存/CPU 分析。当客户需要提升某个命名空间的限制(https://turbopuffer.com/docs/limits)时,我们可当场检查并更新配置(真的可以!)。当所有工具触手可及,你会感觉与基础设施的连接更紧密,运维值班体验也将大为改善。

相似文章

从头构建 PlanetScale:基础设施

Hacker News Top

作者详细介绍了构建 Homescale 的过程,这是一个类似于 Docker 映像和容器模型的数据库工具,能够从不可变快照中创建可写数据库实例和时间点分支,而无需完整数据复制。

数据库流量控制

Hacker News Top

PlanetScale 推出数据库流量控制(Database Traffic Control),这是一款 Postgres 流量管理系统,允许用户对数据库资源实施灵活预算,从而防止因不良查询或失控工作负载导致的过载。

我们如何将CDC推送到Postgres

Hacker News Top

Snowflake的工程博客详细介绍了新的Postgres数据镜像功能,该功能通过snowflake_cdc扩展将更改推送到Iceberg表中,旨在实现弹性、低延迟的事务复制。

SQLite:持久化工作流的全部所需

Hacker News Top

这篇博文认为,SQLite 结合 Litestream 进行异步备份,为许多工作流系统(尤其是 AI 智能体)提供了一种简单而有效的持久化执行方法,无需单独编排层或网络数据库。