インフラ・DevOps
データベースを毎日出荷する方法
How to ship a database every day (turbopuffer.com)
要約
Turbopufferは、顧客の要求に迅速に対応するため、1日に数十回のデータベースアップグレードを展開しています。同社は、パブリックSaaS、シングルテナントSaaS、BYOC(Bring Your Own Cloud)の3つのデプロイメントモデルをサポートし、100以上のクラスターを運用しています。BYOC環境では、顧客のクラウドアカウント内でデータベースを運用する必要があるため、SSHやkubectlによる直接アクセスができません。この問題を解決するため、Turbopufferは各クラスターにローカルエージェントを導入し、Kubernetes CRD(TurbopufferOperation)を使用して、アップグレードなどの操作を状態遷移として管理しています。これにより、外部からのアクセスなしに操作を完了させることができます。
全文翻訳
Sharding: 最大256TBを1つのインデックスにNEW: 名前空間シャーディングで単一の名前空間を最大256TBにスケールアップ
データベースを毎日出荷する方法
2026年8月14日 • Tarun Pothulapati (エンジニア)
毎日、turbopufferの顧客は様々な要求をしてきます。新しいクエリプラン、新しいAPI、新しいインデックス構造などです。それに応えて、私たちはクラスター全体で1日に数十回のデータベースアップグレードを展開しており、その多くはPRを開いたのと同じ日に行われます。
速く出荷することが、すべての顧客に彼らが唯一の顧客であるかのように感じてもらう方法です。私たちはturbopufferを実行できる場所を制限したくありません。そのため、3つのデプロイメントモデル(パブリックSaaS、シングルテナントSaaS、BYOC)で多くのリージョンをサポートしています。合計で、私たちは100以上のクラスターを運用しており、これは6ヶ月前よりも2倍多く、パブリックリージョンと多くのBYOCデプロイメントを追加するにつれて増加しています。
╔═ turbopuffer cloud account ═════════════════════╗ ╔═ customer cloud account ═══╗
║ ║░ ║ ║░ ║ ┏━ public ━━━━━━━━━━┓ ┏━ single-tenant ━━━━━┓ ║░ ║ ┏━ BYOC ━━━━━━━━━━━━━━━┓ ║░ ║ ┃ AWS | GCP ┃ ┃ AWS | GCP ┃ ║░ ║ ┃ AWS | GCP | Azure ┃ ║░ ║ ┃ shared resources ┃ ┃ dedicated resources ┃ ║░ ║ ┃ customer's resources ┃ ║░ ║ ┃ ┃ ┃ ┃ ║░ ║ ┃ ┃ ║░ ║ ┃ tpuf operator ┃ ┃ tpuf operator ┃ ║░ ║ ┃ no tpuf operator ┃ ║░ ║ ┃ access ┃ ┃ access ┃ ║░ ║ ┃ access ┃ ║░ ║ ┗━━━━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━━━━━━┛ ║░ ║ ┗━━━━━━━━━━━━━━━━━━━━━━┛ ║░ ║ ║░ ║ ║░ ╚═════════════════════════════════════════════════╝░ ╚════════════════════════════╝░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░╔═ tpuf account ═══════════╗ ║ ┏ public ━━━━━━━━━━━━━━┓ ║ ║ ┃ AWS | GCP ┃ ║ ║ ┃ shared resources ┃ ║ ║ ┃ tpuf operator access ┃ ║ ║ ┗━━━━━━━━━━━━━━━┛ ║ ║ ┏ single-tenant ━━━━━━━┓ ║ ║ ┃ AWS | GCP ┃ ║ ║ ┃ dedicated resources ┃ ║ ║ ┃ tpuf operator access ┃ ║ ║ ┗━━━━━━━━━━━━━━━┛ ║ ╚══════════════════════════╝ ╔═ customer account ═══════╗ ║ ┏ BYOC ━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ AWS|GCP|Azure ┃ ║░ ║ ┃ customer's resources ┃ ║░ ║ ┃ no tpuf operator ┃ ║░ ║ ┃ access ┃ ║░ ║ ┗━━━━━━━━━━━━━━━┛ ║░ ╚══════════════════════════╝░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░
BYOCの問題
BYOCクラスターは、デフォルトでは資格情報を持たない顧客のクラウドアカウント内に存在します。SSHやkubectlで直接アクセスすることはできません。
一部のBYOCベンダーは、顧客に専用のクラウドアカウントを切り出し、そこにベンダーの常時管理者権限を付与するように求めています。これにより、顧客のクラウドアカウントの残りの部分は分離されますが、各専用アカウントは、私たちが一般的に避けたいセキュリティ監視、コンプライアンス、請求のオーバーヘッドを生み出します。
私たちはBYOCとSaaSで異なるコントロールプレーンを望んでいません。そのため、最低共通 denominadorで設計する必要があります。クラスターに立ち入ることなく、すべての操作を実行できるようにする必要があります。触れることのできないデータベースクラスターをどのように運用するのでしょうか?
これが機能する唯一の方法は、私たちがクラスターに対して実行する必要のあるすべての操作が、私たちが入ることなく実行できることです。クラスターは、中央コントロールプレーンとの接続を失った場合でも、操作を終端状態まで独立して推進できる必要があります。
この解決策は、標準的なKubernetesの仕組みです。各クラスターで、単純なステートマシンを実装するローカルクラスターエージェントを実行します。
TurbopufferOperationと呼ばれる単一のKubernetes CRDが、アップグレードからクリーンアップまで、あらゆる種類の操作を表現します。Kubernetesコントローラーは、各カスタムリソース(CR)を、終端状態に達するまで、再帰ループを通じて状態から状態へと駆動します。
操作はデフォルトで自動的に進行しますが、BYOC顧客は承認またはメンテナンスウィンドウで操作をゲートできます。これらはCRDに待機状態として組み込まれており、承認が与えられたり、ウィンドウが開いたりすると進行します。
╔═ operation lifecycle ═══════════════════════════════════════════╗
║ ║░ ║ ┏━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ REQUIRES_APPROVAL ┃ ║░ ║ ┗━━━━━━━━┯━━━━━━━━━━┛ ║░ ║ │ approve (auto or manual) ║░ ║ ▼ ║░ ║ ┏━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ PENDING ┃ ──▶ ┃ AWAITING_MAINTENANCE_WINDOW ┃ ║░ ║ ┗━━━━━━━━┯━━━━━━━━━━┛ ◀── ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ║░ ║ │ start() ║░ ║ ▼ ║░ ║ ┏━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ RUNNING ┃ ──▶ ┃ AWAITING_EXTERNAL_EXECUTION ┃ ║░ ║ ┗━━━━━━━━┯━━━━━━━━━━┛ ◀── ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ║░ ║ │ poll() ║░ ║ ┌────┴────┐ ║░ ║ ▼ ▼ ║░ ║ ┏━━━━━━━┓ ┏━━━━━━━┓ ║░ ║ ┃SUCCESS┃ ┃FAILURE┃ ║░ ║ ┗━━━━━━━┛ ┗━━━━━━━┛ ║░ ║ ║░ ╚═════════════════════════════════════════════════════════════════╝░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░╔═ operation lifecycle ════╗ ║ ┏━━━━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ REQUIRES_APPROVAL ┃ ║░ ║ ┗━━━━━━━━━━━┯━━━━━━━━━━┛ ║░ ║ ▼ approve ║░ ║ ┏━━━━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ PENDING ┃ ║░ ║ ┗━━━━━━━━━━━┯━━━━━━━━━━┛ ║░ ║ ▼ start() ║░ ║ ┏━━━━━━━━━━━━━━━━━━━━━━┓ ║░ ║ ┃ RUNNING ┃ ║░ ║ ┗━━━━┯━━━━━━━━━━━┯━━━━━┛ ║░ ║ ▼ poll() ▼ ║░ ║ ┏━━━━━━━━━┓ ┏━━━━━━━━━┓ ║░ ║ ┃ SUCCESS ┃ ┃ FAILURE ┃ ║░ ║ ┗━━━━━━━━━┛ ┗━━━━━━━━━┛ ║░ ╚══════════════════════════╝░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░
鍵となるのは、既存および将来のあらゆる操作の種類をモデル化するのに十分汎用的でありながら、リコンサイラーが常に終端状態に向けて駆動できる十分に有限な状態を定義することです。
作業を行うために、私たちはクラスターに立ち入る必要は決してありません。状態はクラスター自身の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も使用しませんが、クラスターはコントロールプレーンから作業をプルし、ステータスをプッシュバックアウトできる必要があります。
システムは、クラスターとコントロールプレーン間の接続損失に対して完全に耐性がある必要があります。コントロールプレーンの障害によって、正常なクラスターがダウンしてはなりません。
接続が失われた場合、クラスターは再接続後に新しい操作を引き継ぐことができるべきであり、コントロールプレーンはクラスターの状態をキャッチアップできるべきです。
これらの目標を達成するために、PlanetScale MySQLデータベースをバックエンドとするAPIサーバーで構成されるカスタム中央コントロールプレーンを構築しました。
各クラスターエージェントには、クラスター固有のAPIキーが付与されます。
操作を取得するために、エージェントは認証されたGETリクエストを介してAPIサーバーを定期的にポーリングします。APIサーバーは、そのクラスターの保留中の操作で応答します。