しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure Cosmos DB の物理パーティションを深く理解する

Cosmos DB を正しく利用する上で一番重要となるのがパーティションキーの設計ですが、Cosmos DB には一言でパーティションと言っても論理パーティションと物理パーティションの 2 種類が存在しています。

この辺りの話は公式ドキュメントでも軽く触れられているので、まずはこちらを読み込んでおいてください。普通に使う分には物理パーティションは Azure 側で全て管理されるので、意識する必要はほぼありません。

データモデリング時のパーティションキーの設計は前者の論理パーティションに相当するものになります。論理パーティションと物理パーティションはそれぞれ制限がありますが、基本は論理パーティションの 20GB 制限のみ気にしておけば良いです。

意識する必要はないと言いましたが、物理パーティションについて知っておくと設計時に役立つこともあるので、自分が知っている範囲の内容で簡単にですがまとめておきます。

物理パーティションの基本

公式ドキュメントにあるように、物理パーティションは Cosmos DB の内部実装であり、実際にクエリなどの処理を行っているコンピューティングの単位になります。1 つの物理パーティションには 4 つのレプリカが存在していて、それぞれでクォーラムが実装されています。

グローバル分散が構成されている場合には、レプリケーションは物理パーティション単位で行われています。

Cosmos DB には最低でも 1 つの物理パーティションが必要になりますが、ストレージが 50GB 以上の場合や 10000 RU 以上が要求された場合には、Azure によって物理パーティションは自動的に増やされていきます。

スループットと物理パーティション

物理パーティションには 10000 RU という性能の上限がありますが、これまでの経験上は RU の上限よりもストレージの 50GB 上限の方に引っかかることが多かったです。

この時問題になるのが 元々 4000 RU を割り当てていたコンテナーのストレージが 50GB を超えて、物理パーティションが 1 から 2 に増えた時の挙動です。設定した RU は物理パーティションで均等に割り振られるので、これまで 1 物理パーティションで 4000 RU まで使えていたのが、2 物理パーティションになるとそれぞれ 2000 RU まで性能の上限値が下がります。4 物理パーティションでは 1000 RU になります。

突然 429 Too Many Requests が発生するようになった場合は、大体この挙動によるものです。物理パーティションが増えた場合には、以下のドキュメントの通りに RU の再調整を行って対応します。

最初から 50GB 以上のデータを保存することが分かっている場合には、物理パーティションが増える時の挙動がボトルネックとなることも多いので、最初から RU を調整して物理パーティションを必要な数だけ増やしておくという戦略も必要となります。

物理パーティションが増やされる際には、新しい物理パーティションに対してレプリケーションが行われるので、完了するまでは RU が追加で消費されます。これは一時的なスループット低下の原因となります。

接続モードと物理パーティション

Cosmos DB のパフォーマンス向上のために C# と Java SDK では Direct モードを使って接続することが推奨されています。この Direct モードは名前の通りバックエンドにある物理パーティションに対して、直接接続を行う仕組みになっているのでオーバーヘッドを削減出来ます。

このような動作を実現するには、指定されたパーティションキーがどの物理パーティションに格納されているかを、SDK 自身が把握している必要があります。

Application Insights を有効化していると、アプリケーションの初期化時に pkranges へリクエストが投げられているのを見たことがある人も多いと思いますが、これが物理パーティションとパーティションキーのマッピングを返す API となっています。

当然ながらユーザーコードから使うことは想定されていませんし、実際に使う必要もありません。

コンテナー毎に最初に使われるタイミングでマッピングを取得している関係上、1 回目のリクエストが遅いという問題が発生するのですが、最新の Cosmos DB SDK for .NET では初期化を明示的に行う API が追加されているので、1 回目のリクエストから素早く実行できるようになっています。

アプリケーションのウォームアップ時に使うことで、アプリケーション起動後は指定したコンテナーに対してのオーバーヘッドを最小化出来ます。

クロスパーティションクエリと物理パーティション

Cosmos DB の設計上、避けておくべきなのがクロスパーティションクエリであり、この原則は変わらないのですが、1 つの物理パーティションで確実に収まるケースでは緩和可能です。

以下のドキュメントでパーティションとして表現されているものは、全て物理パーティションとなっています。1 つの物理パーティションに全ての論理パーティションが収まっている場合には、インデックスが使われるためクロスパーティションクエリでも低コストで実行可能です。

問題となるのは多くの物理パーティションが存在しているコンテナーに対してのクエリです。多くの物理パーティションが存在していると、それぞれでクエリが実行されるのでコストが必然的に高くなります。

クロスパーティションクエリで一番効率が良いのは、1 つの物理パーティションに含まれているパーティションキーのみクエリの条件に含めることです。しかしユーザーコードからはそのような処理は不可能なので、現実的には SDK に用意された機能を使う形になります。

上手くクロスパーティションクエリを使っているのが ReadMany API となります。

詳細は以前のエントリを読んでもらいたいですが、クロスパーティションクエリも上手く組み立てると最小限のコストで利用できるということです。だからと言って全てクロスパーティションクエリにするといった設計は必ず避けましょう。

Change Feed と物理パーティション

物理パーティションと深く関わっているのが Change Feed です。Cosmos DB の Change Feed は論理パーティション単位で順序保証がされていますが、実体は物理パーティション単位での処理となっています。

この辺りの実装は Change Feed の Pull Model を見ると簡単に理解できます。

Pull Model では並列実行のために FeedRange というクラスを使って行いますが、この FeedRange は物理パーティション毎に割り当てられます。FeedRange を複数のマシンにバラまくことで、それぞれの物理パーティションから Change Feed を読み取っての実行が可能になっています。

従って 1 つの Change Feed Processor でも最大並列数は物理パーティションの数となります。要するに物理パーティション単位ではシングルスレッドとして処理されるということなので、Change Feed の順序保証を利用した処理が書きやすくなっています。

その結果として 1 つの Change Feed Processor ではスケールアウトの限界が割と早いです。回避するためには Change Feed Processor の処理を最小化し、その代わりに小さな Change Feed Processor を用途ごとに複数作成する戦略を取ります。

メトリックと物理パーティション

ここまでで物理パーティションについてはほぼ理解が進んだと思うので、モニタリングに入っていきたいと思います。パーティションキーを注意深く選択しても、実際にアプリケーションを稼働させてみると、特定の物理パーティションにデータが偏っていることがよくあります。

その所謂ホットパーティションを確認するにはメトリックを PartitionKeyRangeId で分割して表示します。

最近では Physical Partition Throughput など物理パーティションのメトリックも追加されているので、昔よりはトラブルシューティング時に物理パーティションを意識しやすい環境が出来ています。

Azure Monitor の Cosmos DB Insights でも確認出来るようなので、格段に対応しやすくなっています。

物理パーティションに対する操作(プレビュー)

Build 2022 では Cosmos DB のアップデートがいくつか発表され、物理パーティションを直接操作することでパフォーマンス改善が行える機能がプレビューで追加されています。

Merge partitions は名前の通り物理パーティションをマージする機能です。スループットで書いたように Cosmos DB の RU は物理パーティションで均等に割り振られるので、実際のスループットは物理パーティションが少ない方が高くなります。過剰な物理パーティションをマージして減らすことで最適化を行えます。

もう一つが物理パーティション間でスループットを再分散する機能です。設計時にどれだけ考慮したとしても、現実的にはパーティションキーによっての偏りが発生するのは避けられません。

そのようなケースでホットパーティションとなっている物理パーティションに対して、均等ではなく多く RU を割り当てることで、全体の RU 割り当てを増やすことなくスループットを最適化出来ます。

このように Cosmos DB で物理パーティションを意識すると、極限までパフォーマンスとコストの最適化が行えるようになってきています。可能にはなりますが、必須ではないことを理解しておいてください。

いきなり物理パーティションを意識して設計するのではなく、まずは適切なパーティションキーを選択できるかどうかに Cosmos DB の設計はかかっています。物理パーティションについては後から対応できることばかりなので、最初のデータモデリングに全力を出しましょう。