永久無料のサーバーレスSQLデータベースを構築した方法
私たちは最近サーバーレスの一般提供(GA)を発表し、変更データ取得(CDC)、バックアップと復元、99.99%のアップタイムSLAをサポートしました。CockroachDB Serverlessがどのように機能するのか、そしてなぜ私たちはそれを無料で提供できるのか、限られた期間の無料ではなく無料で提供できるのかを知るために、ぜひお読みください。そこに至るまでには重要かつ魅力的なエンジニアリングが必要でした。このブログでそれを読んだり同僚のエミリー・ホーリンと行った最近のプレゼンテーションを見たりすると楽しんでいただけると思います:
CockroachDB Serverlessとは?
データベースを作ったことがある人なら予想されるトラフィックから使用するサーバーのサイズや台数を見積もったことがあるのではないでしょうか。もし低すぎるとデータベースが負荷で倒れ、停止してしまいます。高すぎると、あるいはトラフィックが爆発的に増加すると、ただ眠っているだけのサーバーに無駄なコストをかけることになります。もっと良い方法はないのでしょうか?
サーバーレスとはサーバーのことを考える必要がないことを意味します。もちろんアプリケーションのリクエストを処理するサーバーは存在しますが、それは私たちの問題であり、あなたの問題ではありません。サーバーの割り当て、設定、メンテナンスなど、舞台裏の大変な仕事はすべて私たちが行います。サーバーにお金を払う代わりに、アプリケーションからデータベースへのリクエストとデータが消費するストレージにお金を払うことになります。
実際に使用した分だけを支払うので、その使用量を前もって把握する必要はありません。使用量が多ければ負荷の増加に対応するためにより多くのハードウェアを自動的に割り当てます。使用量が少なければ支払いは少なくて済みますし、まったくかからないこともあります。また、毎月のリソース上限を設定できるので、請求書に驚かされることもありません。制限値に近づいたら警告を発し、対応策を提示します。
それが一番いいところです。CockroachDB Serverlessはリクエストとストレージのための寛大な(そして終わることのない)毎月のクレジットで「永久無料」です。数回のクリックやAPIコールだけで、数秒でフル機能のCockroachDBデータベースを作成することができます。データセンターの障害に耐える「常時接続」データベースを手に入れ、データの暗号化された複数のコピーを保持するため、ハッカーやハードウェア障害でデータを失うことはありません。アプリケーションに変更を加えることなく、規模の大小にかかわらず、お客様のニーズに合わせて自動的に透過的に拡張します。オンラインスキーママイグレーション、Postgresとの互換性をサポートし、エンタープライズ機能への無制限のアクセスを提供します。
CockroachDB Serverlessを使うからといってAWS LambdaやGoogle Cloud Functionsのようなサーバーレスサービスを使わなければならないわけではありません(ただし、それも素晴らしいツールです!)。
どうしてこれを配る余裕があるのでしょうか?まあ、確かに私たちは皆さんの中から「大きくなる」成功したアプリを作り、お金を払う顧客になってくれる人が出てくることを期待しています。しかしそれ以上に、私たちは革新的なサーバーレスアーキテクチャを構築し、単一の物理的なCockroachDBデータベースクラスタ上に何千もの仮想化CockroachDBデータベースクラスタを安全にホストすることができるようにしました。つまり、数キロバイトのストレージとわずかなリクエストを持つ小さなデータベースは物理ハードウェアのわずかなスライスで実行されるため、実行コストはほとんどかからないのです。この仕組みは後ほど詳しく説明しますが、ここでは考えやすいように図を示します:
シングルテナントアーキテクチャ
これまで1つの物理的なCockroachDBクラスタは1人のユーザーや組織による専用利用を想定していました。これはシングルテナントと呼ばれるものです。過去数回のCockroachDBのリリースで私たちは静かにマルチテナンシーのサポートを追加しており、物理的なCockroachDBクラスタを複数のユーザーや組織(「テナント」と呼ぶ)で共有することができます。各テナントは物理的なCockroachDBクラスタ上でホストされ、かつ他のテナントのクラスタから安全かつ分離された独自の仮想化CockroachDBクラスタを取得します。仮想マシン(VM)がどのように機能するかは皆さんよくご存知ですよね?データベースクラスタに限って言えば、そんな感じです。
マルチテナントの仕組みを説明する前に、シングルテナントアーキテクチャをおさらいしておく必要があります。まず、シングルテナントのCockroachDBクラスタは任意の数のノードで構成されています。各ノードはデータの保存と計算の両方に使用され、通常は自分のマシンでホストされます。1つのノードの中でCockroachDBはレイヤーアーキテクチャを持ちます。最も高いレベルにあるのはSQL層でSQL文を解析し、最適化し、実行します。これは高レベルのSQLステートメントを単純な読み取りおよび書き込み要求に巧みに変換して、基盤となるキーバリュー(KV)層に送信することで行われます。
KVレイヤーはトランザクション、分散、複製されたキーバリューストアを維持します。ということで分解してみます。各キーは辞書のように任意の値に対応する一意の文字列です。KVではこれらのキーと値のペアをソートして保存し、高速に検索できるようにしています。また、複数のキーと値のペアは範囲にグループ化されます。各範囲はキーでソートされたキーバリューペア全体のうち、連続した重複しない部分を含んでいます。範囲は利用可能なノードに分散され、高可用性のために少なくとも3回レプリケートされます。キー・バリュー・ペアはオール・オア・ナッシング・トランザクションで追加、削除、更新することができます。以下は高レベルのSQLステートメントが、単純なKV GET呼び出しに変換される例を簡略化して示しています:
シングルテナントのCockroachDBではSQL層は各ノードでKV層と同じプロセスで同居しています。SQL層は常に同じノード上で動作するKVインスタンスに呼び出しますが、KVは他のノード上で動作するKVの他のインスタンスに追加の呼び出しを「ファンアウト」することがよくあります。これはSQLが必要とするデータがクラスタ内のノードに散在する範囲に配置されていることが多いためです。
マルチテナントアーキテクチャ
そのシングルテナントアーキテクチャを拡張して複数のテナントをサポートするにはどうすればいいのでしょうか。各テナントは自分専用のCockroachDBクラスタを持っているように感じられ、パフォーマンスとセキュリティの面で他のテナントから隔離されている必要があります。しかし、SQLレイヤーをテナント間で共有しようとすると、それを実現するのは非常に困難です。あるテナントのSQLクエリが暴走すると、同じプロセスにいる他のテナントのパフォーマンスが簡単に乱されてしまいます。さらに、同じプロセスを共有すると確実に軽減することが難しい多くのテナント横断的なセキュリティ脅威が発生します。
これらの問題に対する1つの解決策として、各テナントにSQL層とKV層の両方を実行する独立したプロセスの独自のセットを与えることが考えられます。しかし、この場合はテナント間でキーバリューストアを共有できなくなるという別の問題が発生します。そうなると、マルチテナント型アーキテクチャの大きな利点の1つである多数の小さなテナントのデータを共有ストレージ層に効率的にまとめることができなくなります。
この問題を熟考した結果、あるコンポーネントを分離して他のコンポーネントを共有することで、このジレンマをエレガントに解決できることに気づきました。SQLレイヤーは共有が難しいので、KVレイヤーのトランザクションとディストリビューションのコンポーネントと一緒に、テナントごとのプロセスで分離することにしました。一方、KVのレプリケーションとストレージのコンポーネントは全テナントで共有されているストレージノードで実行され続けます。このように分離することで、テナントごとのSQLプロセスのセキュリティと分離、共有ストレージノードの効率性という「両者の良いとこ取り」を実現しています。以下は分離された2つのテナントごとのSQLノードが共有ストレージ層と相互作用する様子を示した最新の図です:
ストレージノードはテナントSQLクエリを実行しなくなりましたが、シングルテナントのCockroachDBを支える洗練されたインフラを活用しています。ノードの障害はデータの可用性に影響を与えることなく検出・修復されます。各レンジの読み込みと書き込みを調整するリーズホルダーはアクティビティに応じて移動します。忙しいレンジは自動的に分割され、静かなレンジは統合されます。レンジは負荷に応じてノード間でリバランスされます。ストレージ層は、ホットレンジをメモリにキャッシュし、コールドレンジをディスクにプッシュします。アベイラビリティ・ゾーンをまたぐ3ウェイ・レプリケーションにより、データの安全な保存と高い可用性を保証します。
このアーキテクチャをご覧になって共有ストレージノードのセキュリティについて疑問を持たれたかもしれません。私たちはテナントのデータを保護するための強力なセキュリティ対策の設計と実装に多大な時間を費やしました。各テナントはKVの鍵空間のうち隔離されて保護された部分を受け取ります。これはSQLレイヤーが生成するすべてのキーの前にテナント固有の識別子を付けることで実現されています。<table-id>/<index-id>/<key>
のようなキーを生成するのではなく、SQLは/<tenant-id>/<table-id>/<index-id>/<key>
といったキーを生成します。つまり、異なるテナントが生成したキー・バリュー・ペアはそれぞれの範囲に隔離されます。さらにストレージノードはSQLノードからの通信をすべて認証し、各テナントが自分のテナント識別子を先頭に持つキーにしか触れられないようにします。
セキュリティ以外にもテナント間で基本的なサービス品質を確保することにも気を配りました。複数のテナントからのKV呼び出しによって、ストレージノードに負荷がかかる恐れがある場合はどうするのか。その場合、CockroachDBのアドミッション・コントロールが機能します。アドミッション・コントロール・システムはGoスケジューラと統合され、テナント間の公平性を確保するための作業キューを維持します。各テナントのGET、PUT、DELETEリクエストにはCPU時間とストレージI/Oがほぼ均等に割り当てられます。これにより、1つのテナントがストレージノードのリソースを独占することがないようにします。
サーバーレスアーキテクチャ
待てよ...最後のセクションはサーバーレスアーキテクチャについてではなかったのか?そうですね、イエスでもありノーでもあります。説明したように、私たちはマルチテナンシーをサポートするためにコアとなるデータベース・アーキテクチャに大幅なアップグレードを施しました。しかし、それは話の半分に過ぎません。サーバーレスを実現するためにはマルチテナントのCockroachDBクラスターをデプロイして運用する方法についても大きな改良を加える必要がありました。
当社のマネージドクラウドサービスではKubernetes(K8s)を使用して共有ストレージノードとテナントごとのSQLノードの両方を含むサーバーレスクラスターを運用しています。各ノードはK8sのポッドで動作していますが、これはDockerコンテナに仮想化されたネットワークと制限されたCPUとメモリ容量を搭載したものに過ぎません。さらに掘り下げるとプロセスのCPUとメモリの消費量を確実に制限できるLinuxのcgroupが発見されます。これにより、テナント単位でSQLリソースの消費量を簡単に計測・制限することができます。また、同じマシンでスケジュールされているポッド間の干渉を最小限に抑え、他のテナントが重いワークロードを実行しているときでも各テナントに高品質のエクスペリエンスを提供することができます。
以下は、典型的なセットアップがどのようなものかをハイレベルで(簡略化して)表現したものです:
K8sクラスタにある「プロキシポッド」は何をしているのでしょうか?かなり便利なものであることがわかりました:
- 多くのテナントが同じIPアドレスを共有できるようにするためです。新しい接続が到着すると、プロキシは受信したPostgres接続パケットを「スニッフィング」して、SNIヘッダーやaPG接続オプションからテナント識別子を探します。これでどのSQLポッドにその接続をルーティングすべきかがわかります。
- 「最小接続数」アルゴリズムを使用してテナントの利用可能なSQLポッド間で負荷分散を行います。
- サービスの悪用が疑われる場合はそれを検知して対応します。これはお客様のデータを保護するためのセキュリティ対策の1つです。
- 非アクティブのために一時停止されたテナント・クラスタを自動的に再開します。これについては以下の「スケーリング」のセクションで詳しく説明します。
クラウドロードバランサーが新しい接続をプロキシポッドの1つにルーティングすると、プロキシポッドはその接続を接続先のテナントが所有するSQLポッドに転送します。各SQLポッドは1つのテナント専用で、同じテナントが複数のSQLポッドを所有することができます。ネットワーク・セキュリティ・ルールにより、同じテナントが所有していない限り、SQLポッドは互いに通信することができません。最後にSQLポッドはKVレイヤーを介して通信し、共有ストレージポッドが管理するデータにアクセスします。各ポッドはAWS EBSやGCP PDなどのクラウドプロバイダーのブロックストレージシステムにデータを保存します。
サーバーレスクラスターの優れた点の1つは、その作成が非常に速いということです。通常の専有されたクラスタはクラウドプロバイダのプロジェクトの作成、新しいVMのスピンアップ、ブロックストレージの取り付け、IPやDNSアドレスの割り当てなどが必要なため、起動に20~30分かかります。これに対してサーバーレスクラスタはK8sがすでに管理している既存のVM上に新しいSQLポッドを作成するよう指示するだけなので、作成にかかる時間はわずか数秒です。
作成スピードだけでなく、サーバーレスのSQLポッドには大きなコストメリットもあります。SQLポッドはVM上にまとめて配置することができ、同じOS、利用可能なCPUやメモリを共有することができます。このため、ワークロードが極小の「ロングテール」テナントがそれぞれハードウェアの小さなスライスを使用できるため、運用コストを大幅に削減できます。これに対し、専用VMは一般的に少なくとも1つのvCPUと1GBのメモリーを確保する必要があります。
スケーリング
テナントが所有するデータ量が増え、そのデータにアクセスする頻度が高くなると、テナントのデータはより多くのKVレンジに分割され、より多くの共有ストレージポッドに分散されることになります。この種のデータスケーリングはCockroachDBではすでに十分にサポートされており、マルチテナントクラスターでもシングルテナントクラスターとほぼ同じように動作します。ここでは、この種のスケーリングについてこれ以上詳しく説明するつもりはありません。
同様に、あるテナントのデータに対して実行されるSQLクエリやトランザクションの数が増えると、そのテナントに割り当てられるコンピュートリソースもそれに比例して大きくなる必要があります。あるテナントのワークロードは実行に数十、数百のvCPUを必要とするかもしれませんが、別のテナントのワークロードはvCPUのほんの一部しか必要としないかもしれません。実際、ほとんどのテナントがCPUをまったく必要としないことが予想されます。これはCockroachDB Serverlessを試す開発者の大部分が単に「試しに使ってみる」だけだからです。彼らはクラスタを作成し、それに対していくつかのクエリを実行するかもしれませんが、その後はおそらく永久にそれを放棄することになるでしょう。自分のクラスタのためにvCPUの一部をアイドル状態にしておくだけでも、非アクティブなクラスタすべてと掛け合わせれば、膨大なリソースの無駄遣いになってしまいます。また、定期的にクラスタを使用しているテナントであってもSQLのトラフィック負荷は一定ではなく、日ごと、時間ごと、あるいは秒ごとに大きく変動する可能性があります。
CockroachDB Serverlessはこのような幅広いリソースのニーズの移り変わりにどのように対応しているのでしょうか。各テナントの秒単位のトラフィック負荷に基づいて、適切な数のSQLポッドを動的に割り当てることによってです。新しいキャパシティは最良の場合は即座に、最悪の場合は数秒以内に割り当てることができます。これにより、テナントのトラフィックが極端に急増した場合でも低レイテンシーでスムーズに処理することができます。同様にトラフィックが減少すると、不要になったSQLポッドをシャットダウンし、残りのSQL接続をそのテナントの他のポッドに透過的に移行することができます。トラフィックがゼロになりSQL接続が残らなくなった場合、そのテナントが所有するすべてのSQLポッドが終了されます。新しいトラフィックが到着すると、数百ミリ秒以内に新しいSQLポッドをスピンアップし直すことができます。これにより滅多に使用されないCockroachDB ServerlessクラスタがCockroach Labsにほとんどコストをかけず、ユーザーにもまったくコストをかけずに、本番レベルのレイテンシーを提供することができます。
このような応答性の高いスケーリングが可能なのはマルチテナントのCockroachDBがSQLレイヤーをKVストレージレイヤーから分離しているためです。SQLポッドはステートレスなのでテナントデータの一貫性や耐久性に影響を与えることなく、自由に作成・破棄することができます。すべてのデータの一貫性と可用性を確保するために、ステートフルストレージポッドで行う必要があるようなポッド間の複雑な連携や、ポッドの慎重なコミッショニングとデコミッショニングは必要ありません。通常、長時間稼働し続けるストレージポッドとは異なり、SQLポッドはエフェメラルであり、起動後数分でシャットダウンされる可能性があります。
オートスケーラー
スケーリングの仕組みについて、もう少し掘り下げてみましょう。サーバーレスクラスタにはオートスケーラーというコンポーネントがあり、各テナントに割り当てるべき理想的なSQLポッドの数を1つ、多数、またはゼロのいずれかに決定する役割を担っています。オートスケーラーはクラスタ内の各SQLポッドのCPU負荷を監視し、2つのメトリクスに基づいてSQLポッドの数を計算します:
- 過去5分間の平均CPU使用率
- 直近5分間のCPU使用率のピーク値
平均CPU使用率はテナントに割り当てられるSQLポッドの「ベースライン」数を決定します。ベースラインではSQLポッドを意図的に過剰にプロビジョニングすることで、インスタントバースト用に各ポッドで利用可能な予備のCPUを確保します。しかし、ピーク時のCPU使用量がオーバープロビジョニングされた閾値を超えた場合、オートスケーラーはSQLポッドの数をベースラインより増やすことで対応します。このアルゴリズムは移動平均の安定性と瞬時最大値の応答性を兼ね備えています。オートスケーラは頻繁すぎるスケーリングを避けることができますが、それでも負荷の大きなスパイクをすばやく検出して反応することができます。
オートスケーラーが理想的なSQLポッドの数を導き出すと、K8sの調整プロセスが起動し、理想的な数に達するようにポッドを追加または削除します。次の図は考えられる結果を示しています:
図に示すように、私たちは「あらかじめ温めておいた」ポッドのプールを管理しており、テナントの識別子とセキュリティ証明書を「スタンプ」するだけで、すぐに利用できるようになっています。K8sがゼロからポッドを作成するのに20〜30秒かかるのに対し、この作業はほんの1秒程度で済みます。その代わり、ポッドを削除する必要がある場合、突然終了させることはありません。それは、そのポッドへのすべてのSQL接続を無作法に終了させることになるからです。むしろ、ポッドは消耗状態に置かれ、より優雅にSQL接続を終了させる機会を得ます。一部の接続はアプリケーションによって閉じられるかもしれませんが、他の接続はプロキシによって、排出中のポッドからまだアクティブな他のポッドに透過的に移行されます。排出中のポッドは、すべての接続がなくなるか、10分が経過するか、どちらか早いほうで終了させられます。
アプリケーションの負荷がゼロになるとオートスケーラーは最終的にテナントを停止することを決定し、テナントのすべてのSQLポッドが削除されます。テナントがSQLポッドを所有しなくなると、CPU、I/O、帯域幅を消費することはありません。唯一のコストはデータの保存費用で、これは他のリソースと比較して比較的安価です。これが私たちが皆さんにデータベースクラスターを無料で提供できる理由の1つです。
しかし、解決しなければならない問題が1つ残っています。テナントにSQLポッドが割り当てられていない場合、テナントはどのようにしてそのクラスターに接続できるのでしょうか?その質問に答えるにはすべてのサーバーレスクラスタでプロキシポッドのセットが実行されていることを思い出してください。外部クライアントが開始する各SQL接続はプロキシポッドによって傍受され、テナントに割り当てられたSQLポッドに転送されます。しかし、プロキシが現在テナントに割り当てられたSQLポッドがないことを発見した場合、オートスケーラーがスケーリングに使用するのと同じK8s和解プロセスをトリガーします。新しいポッドがSQLポッドの予熱されたプールから引き出され、スタンプが押され、接続に利用できるようになります。再開プロセス全体にかかる時間は数分の一秒ですが、この時間をさらに短縮するために積極的に取り組んでいます。
まとめ
CockroachDB Serverlessの仕組みがわかったところで、ぜひ https://cockroachlabs.cloud/ を試してみてください。この中で何か質問があれば、私たちのコミュニティSlackチャンネルに参加して質問してください。また、CockroachDB Serverlessを使った経験やポジティブなフィードバック、ネガティブなフィードバックもお待ちしています。私たちは今後数ヶ月間、CockroachDBを改善するために努力していきますので、アカウント登録をして、私たちの進歩に関する最新情報を入手してください。そして、私たちがCockroachDBを次のレベルに引き上げるのを手伝ってくれるなら、私たちは採用中です。
その他のリソース
- 無料オンライン講座: サーバーレスデータベースとCockroachDB Serverlessの紹介。このコースではサーバーレスデータベースの背後にあるコアコンセプトを紹介し、CockroachDB Serverlessを開始するために必要なツールを提供します。
- あなたの意見を聞かせてください: Slackのコミュニティに参加して、あなたの考えを教えてください!
- 私たちは採用中です!CockroachDBを開発するチームに参加しませんか。リモートフレンドリー、ファミリーフレンドリー、データとアプリ開発における最大のチャレンジに挑みます。
Discussion