インフラ・DevOps
ゼロからPlanetScaleを構築しよう:インフラストラクチャ
Let's Build PlanetScale from Scratch: Infrastructure (onatm.dev)
要約
この記事は、PlanetScaleのようなデータベーススケーリングソリューションを自宅で再現するプロジェクト「Homescale」の第一弾です。著者は、データベースのストレージ層とコンピューティング層を分離し、イミュータブルなスナップショットからコピーなしで書き込み可能なインスタンスやブランチを作成するアイデアを探求しています。
全文翻訳
Homescale、自宅でのPlanetScale。私は何年も前にデータベースツール会社で働いており、PlanetScaleがクールになる前にいくつかのデータベースクローニングツールを構築する幸運に恵まれました。それらのツールはPlanetScaleほど成功する運命にはありませんでしたが、その用途がありました。それらのツールの背後にあるアイデアはシンプルでした:ストレージ層をコンピューティングから分離し、データベースファイルをクローンするために最適なストレージ技術を使用すること。このアイデアはしばらく前に、Rust開発者でCephのハンズオン経験を持つ人物を探していたF1チームとのインタビューについてツイートしていたときに再び私に戻ってきました。Cephに言及したことで、私が関わった失敗した製品の試みを思い出し、友人に冗談でHomescale、自宅でのPlanetScaleを構築すると言いました。
Homescale
私はgithub.com/homescale-dev/homescaleでHomescaleを構築しています。Homescaleは、データベース全体をコピーすることなく、イミュータブルなスナップショットから書き込み可能なデータベースインスタンスとポイントインタイムブランチを作成します。私はDockerのイメージとコンテナモデルをデータベースの状態を表すために借用しました:データベースイメージはイミュータブルな出発点です。データベースコンテナは、そのイメージから作成された書き込み可能なクローンです。ブランチは、既存のコンテナの現在の状態から作成された別のコンテナです。
私が考えているCLIは次のようになります。
homescale image create --engine postgres postgres-base
homescale container create --image postgres-base dev-db
homescale container connect dev-db
homescale branch create --container dev-db feature-login
homescale container connect feature-login
このシリーズの例ではPostgresを使用していますが、Homescaleのストレージモデルはデータベースに依存しません。イメージ、コンテナ、ブランチのモデルは、ブロックデバイスによってバックアップされたファイルシステムに永続状態を保持し、リカバリ可能なスナップショットのために準備できる任意のデータベースエンジンに適用できます。Postgresは私がサポートする最初のエンジンであり、ストレージとオーケストレーションレイヤーを構築する際に具体的なものを提供してくれます。エンジン固有の動作はアダプターの後ろに配置されます。そのアダプターは、イメージを初期化し、データベースプロセスを開始し、接続詳細を公開し、必要に応じてスナップショットのためにデータベースを準備します。Homescaleはそれを取り巻くライフサイクルを処理します:ボリューム、イミュータブルな状態、書き込み可能なクローン、ワークロード、およびリネージ。
ブランチという言葉は、コンテナ間の関係を説明します。これらのコマンドを実行した後、dev-dbとfeature-loginはどちらも書き込み可能なデータベースコンテナです。feature-loginは、ブランチが作成された時点のdev-dbの状態から開始されただけです。
下では、リネージは次のようになります。
フローチャート LR
Image[/postgres-base<br/>image/] -->|clone| Dev[dev-db<br/>writable]
Dev -->|snapshot| State[/read-only<br/>state/]
State -->|clone| Feature[feature-login<br/>writable]
イメージはすでにイミュータブルなので、Homescaleはそれを直接dev-dbから作成できます。書き込み可能なコンテナからブランチを作成するには、中間状態が必要です。Homescaleはまずポイントインタイムでdev-dbをキャプチャし、次にその状態からfeature-loginを作成します。feature-loginを作成することは、dev-dbをバイト単位でコピーすることを意味できません。100 GBのデータベースは、ブランチが開始される前にさらに100 GBのストレージを必要とします。ブランチは、初期段階では、キャプチャされたdev-dbの状態とデータを共有する必要があります。その後変更されるデータのみが追加のストレージを必要とします。そのためには、データベースプロセスよりも下のストレージレイヤーでブランチングが発生する必要があります。
ストレージとコンピューティングの分離
データベースストレージとコンピューティングの分離は新しいアイデアではありません。標準的なAmazon RDSエンジンは、EBSボリュームにデータベースファイルとログファイルを格納します。Google Cloud SQLは、マシンシリーズに応じてPersistent DiskまたはHyperdiskを使用して、ネットワークブロックストレージがアタッチされたVMでデータベースプロセスを実行します。データベースは通常のブロックデバイスを見ますが、その永続データはコンピューティングホストのローカルディスクに結び付けられていません。Aurora、AlloyDB、Azure SQL Hyperscaleはこれをさらに進めています。それらのコンピューティングインスタンスは、各インスタンスのデータの完全なコピーを作成することなく、データベース専用に設計された共有または分散ストレージシステムに接続します。
Homescaleは最初のモデルに近いです。データベースプロセスは、ブロックデバイスのように見えるもの上のファイルシステムを読み書きします。私は、そのデバイスの背後にあるストレージが、それを使用するプロセスとは独立したライフサイクルを持つだけでよいのです。
フローチャート TD
Database[Database process]
Filesystem[Filesystem]
Device[Block device]
Storage[(Persistent storage)]
Database -->|file reads and writes| Filesystem
Filesystem -->|block I/O| Device
Device --> Storage
その境界により、Homescaleはデータベースプロセスを開始する前にストレージを作成し、プロセスが停止した後もそれを保持できます。さらに重要なのは、データベースエンジン自体にブランチングを実装させることなく、ストレージレイヤーでブランチングを可能にすることです。2つのPostgresプロセスは、共有履歴について何も知りません。それぞれが独自の書き込み可能なブロックデバイスを見ています。サポートされている別のエンジンも同じストレージ境界を見るでしょう。エンジンアダプターは変更されますが、スナップショット、クローン、リネージモデルは変更されません。
Homescaleはまだ、変更されていないデータを親と共有する書き込み可能なブロックデバイスを作成する方法を必要としています。COWは秘密のソースです。
Copy-on-write(COW)により、書き込み可能なクローンは、作成元のイミュータブルな状態と変更されていないデータを共有できます。Homescaleがdev-dbを作成すると、新しいコンテナは初期段階でpostgres-baseからデータを読み取ります。dev-dbによって行われた書き込みは、イメージを変更せずにコンテナに格納されます。feature-loginの作成も同様のプロセスを繰り返します。Homescaleはdev-dbの現在の状態をキャプチャし、次にそれから新しい書き込み可能なコンテナを作成します。キャプチャされた状態、dev-db@feature-loginは読み取り専用です。これは2つのコンテナが分離された時点を保持します。dev-dbは変更を続けることができますが、feature-loginはそのキャプチャされた状態の上に独自の変更を格納します。feature-loginが変更していないデータを読み取るとき、ストレージレイヤーはそのチェーンを親までたどります。これは、データが見つかるまで複数の世代を通過できます。共有データへの最初の書き込み時に、ストレージレイヤーは関連する割り当てユニットを書き込み可能なコンテナにコピーし、そこで変更を適用します。したがって、100 GBのデータベースのブランチは、最初に100 GBのコピーを必要とせずに、完全な100 GBデータベースとして表示されます。その初期コストは主にメタデータです。dev-dbとfeature-loginが分岐するにつれてストレージ使用量は増加しますが、小さなデータベースの書き込みは、下でより大きなストレージ割り当てを引き起こす可能性があります。このモデルは依存関係も作成します。feature-loginは、自身のコンテナにコピーしていないデータのために、dev-db@feature-loginに依存します。Homescaleは、ブランチがまだそれに依存している間、その中間状態を削除してはなりません。したがって、Homescaleは永続的なブロックデバイス、イミュータブルなスナップショット、および書き込み可能なCOWクローンを必要とします。
Ceph
CephはRBDを通じてそれらの操作を提供します。データベースは通常のブロックデバイスを見ます。Cephは、同じ基盤システム上でオブジェクト、ファイル、およびブロックストレージを提供します。私はブロックインターフェース、RADOS Block DeviceまたはRBDと呼ばれるものだけに関心があります。RBDイメージは、マシンにマッピングされ、ファイルシステムでフォーマットされ、通常のディスクのようにマウントできる仮想ブロックデバイスです。以前のストレージスタックでは、下部にある永続ストレージはCeph RBDイメージです。データベースは、同じファイルシステムとブロックデバイスインターフェースを通じて読み書きを続けます。そのRBDイメージの下では、Cephはデバイスをオブジェクトに分割し、それらをオブジェクトストア全体に格納します。Cephのドキュメントでは、RBDイメージはRADOS内のオブジェクト上にストライプ化されたブロックデバイスとして説明されています。デフォルトのオブジェクトサイズは4 MBですが、設定可能です。これが、小さなデータベースの書き込みが、クローンで同等の小さな割り当てを必ずしも作成しない理由です。クローンが最初に親と共有しているデータに書き込むとき、Cephは書き込みを適用する前に対応するRADOSオブジェクトを子にコピーする必要がある場合があります。RBDスナップショットは、ポイントインタイムでのイメージの読み取り専用ビューです。クローンは、そのスナップショットを参照して、それからデータを取得する書き込み可能なRBDイメージです。