Azure Cosmos DBの物理パーティション数はスケーリングに応じて自動で増加するが減少はしない

この記事は[「NEXTSCAPE Advent Calendar 2020」](https://qiita.com/advent-calendar/2020/nextscape)の12日目です。

本記事ではAzure Cosmos DBのスケーリングとパーティションの関係について書きます。最初に結論だけ述べると、
__一度物理パーティションの数が増加するようなスケーリングを行うと、その後物理パーティションの数が減少するようなスケーリングを行うことはできず、結果的にRUの割り当てに無駄が生じるので注意が必要です__
という話になります。

※本記事は2020年12月時点での情報となります。

# Azure Cosmos DBとは
[Azure Cosmos DB](https://docs.microsoft.com/ja-jp/azure/cosmos-db/introduction)とはMicrosoft Azure上で提供されるフルマネージドのデータベースサービスです。特徴としては以下が挙げられます。

- NoSQL
- グローバル分散（高スループットと高可用性の実現）
- 複数のデータモデルをサポート（JSONドキュメント、キーバリュー、グラフ）
- 弾力性

## RUとは
また、Azure Cosmos DBでは、データベース操作のコストはRU: Request per Units（要求ユニット）という名の独自の概念で表現されます。RUによってスループットと料金コストが決定されます。[公式の文章](https://docs.microsoft.com/ja-jp/azure/cosmos-db/request-units)を引用すると以下になります。
> 1 KB の項目をポイント読み取りする (つまり、ID とパーティション キーの値で 1 つの項目をフェッチする) コストは、1 要求ユニット (または 1 RU) です。 その他のすべてのデータベース操作にも、同様に RU を使用してコストが割り当てられます。 Azure Cosmos コンテナーの操作にどの API 使用するかに関係なく、コストは RU によって測定されます。 データベース操作が書き込み、ポイント読み取り、またはクエリのいずれの場合でも、コストは常に RU で測定されます

割り当てるRUはAzureポータルから変更可能であり、このことがスケーラビリティを実現しています。

# パーティションとは
一般的にパーティションとは、大規模なデータベースを小さなデータベースに分割するときの、個々の小さなデータベースを指します。また、この分割のことをパーティショニングと呼びます。

パーティショニングを行う主な理由は、スケーラビリティです。それぞれのパーティションは別々のノードに配置することができます。大規模なデータセットを数多くのディスクに分散配置できるので、クエリの負荷を大量のプロセッサに分散することができます。

Azure Cosmos DBの場合、2種類のパーティションがあります。それが論理パーティションと物理パーティションです。

## Azure Cosmos DBの論理パーティション
論理パーティションとは、特定のプロパティの値をもとに（論理的に）パーティショニングすることです。
[公式の文章](https://docs.microsoft.com/ja-jp/azure/cosmos-db/partitioning-overview)を引用すると以下になります。
> 論理パーティションは、同じパーティション キーを持つ一連のアイテムで構成されます。 たとえば、食物の栄養に関するデータが含まれているコンテナーでは、すべてのアイテムに foodGroup プロパティが含まれています。 このコンテナーのパーティション キーとして foodGroup プロパティを使用できます。 Beef Products、Baked Products、Sausages and Luncheon Meats など、foodGroup に特定の値を持つアイテムのグループが、個別の論理パーティションを形成します。
Azureポータルから設定できるパーティションキーはこれに相当します。

## Azure Cosmos DBの物理パーティション
一方物理パーティションは、実コンピューティングリソースでの分割を表します。内部的には、1 つまたは複数の論理パーティションが単一の物理パーティションにマップされます。

重要な仕様として、以下があります。
> コンテナー内の物理パーティションの数は、次の構成によって決まります。
- プロビジョニングされたスループットの数 (個々の物理パーティションからは、1 秒あたり最大 10,000 の要求ユニットのスループットを提供できます)。
- データ ストレージの合計 (個々の物理パーティションには、最大 50 GB のデータを格納できます)。

論理パーティションと物理パーティションについては図で書いた方がわかりやすいかもしれません。
[蒼の王座さんの記事](https://sqlazure.jp/b/cosmosdb/cosmic/2861/)の図が一例です。

# Azure Cosmos DBのパーティションとスケーリングの関係
物理パーティション1つに対し、スループットとストレージについては以下の制約が存在します。
1. スループットの上限10,000RU/s
2. ストレージの上限50GB

この制約に関連し、Azure Cosmos DBには以下の仕様が存在します。
1. 物理パーティション1つに対し10,000RU/sを超えるスループットを割り当てた場合、__物理パーティションが自動で分割、増加する__。
2. 物理パーティション1つに対し50GBを超えるデータを格納しようとした場合、__物理パーティションが自動で分割、増加する__。

また、パーティションとRUの関係について、__プロビジョニングされたスループットは、物理パーティション間で均等に分割される__仕様が存在します。

このため、スループットのスケーリングを行うユースケースにおいて、以下が発生することに注意が必要です。
1. RUを増加させていくと、物理パーティションが自動で分割される
2. その後RUを減少させても、物理パーティションは分割されたままとなる
3. その結果、__RUの割り当てが物理パーティションの数だけ按分され、パフォーマンスが劣化する__

## 実演
適当なコンテナを作成し、400RUの状態でデータを投入します。このときメトリックから物理パーティションの数が1つであることが確認できます。
![singlepartition.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322008/85401efa-0216-aaf0-566a-6702c32df30a.png)

この後、RUを10100RUまで上げます。
![scaling.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322008/6904fcb3-f7e2-9b55-245a-43ec3006f2c5.png)

そして再度データを投入すると、メトリックから物理パーティションの数が2つになっていることが確認できます。これはスケーリングに応じて物理パーティションが自動で分割、増加するためです。
![partition2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322008/e2dc7107-2089-9bd5-abc3-35358b44cb0f.png)

この後、RUを400RUまで下げます。
![ru400.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322008/1a64eff3-59d2-7a0e-9bb4-701cd925bde2.png)

RUを下げても物理パーティションの数は2つのままとなります。これはスケーリングに応じて物理パーティションが自動で減少しないためです。
![partition3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322008/77594f67-8411-73a3-cb0e-c7245209da91.png)

## 注意すべきユースケース
「一時的にRUを増加させ、その後もとに戻す」ユースケースとしては、自分がかかわった範囲内では以下のような具体例が挙げられます。
1. データ移行に伴い、大量のデータを短時間で移行するためのスケーリング
2. Web APIサーバ側の性能テストを行うため、そのバックエンドであるCosmos DB側でRU超過を発生させないようにするためのスケーリング

上記のようなユースケースにおいて、注意すべき仕様となります。

# まとめ
本記事では、Azure Cosmos DB、パーティショニング、RUについて簡単な説明をし、スケーリングにおける物理パーティションの注意すべき仕様について記載しました。すなわち、一度物理パーティションの数が増加するようなスケーリングを行うと、その後物理パーティションの数を減少させることはできず、スループットの割り当てに無駄が生じるという仕様です。

公式のフィードバックフォーラムにも[物理パーティションの縮小機能を要望する投稿](https://feedback.azure.com/forums/263030-azure-cosmos-db/suggestions/36636625-improve-scaling-down-experience-remove-redundant)があるのですが、2019年1月からずっとステータスはplannedのままであり、しばらくは対応がなされない雰囲気を感じています。

したがってAzure Cosmos DBで10,000RU/sを超えるスループットや50GBを超えるデータを扱う場合、これらの仕様を理解したうえで、意図しないパフォーマンスの劣化を回避するよう注意する必要があることを覚えておくとよいと思います。

以上、ここまで読んでいただき誠にありがとうございました。

# 出典
※URLはすべて2020年12月12日アクセス
[データ指向アプリケーションデザイン](https://www.oreilly.co.jp/books/9784873118703/)　6章「パーティショニング」
[Azure Cosmos DB の概要](https://docs.microsoft.com/ja-jp/azure/cosmos-db/introduction)
[Azure Cosmos DB でのパーティション分割と水平スケーリング](https://docs.microsoft.com/ja-jp/azure/cosmos-db/partitioning-overview)
[Azure Cosmos DBの要求ユニット](https://docs.microsoft.com/ja-jp/azure/cosmos-db/request-units)
[Azure Cosmos DB の自動スケーリングでプロビジョニングされたスループットについてよく寄せられる質問](https://docs.microsoft.com/ja-jp/azure/cosmos-db/autoscale-faq#what-is-the-mapping-between-the-max-rus-and-physical-partitions)
[Cosmos DB のパーティショニング - 蒼の王座](https://sqlazure.jp/b/cosmosdb/cosmic/2861/)
[Improve scaling down experience: remove redundant physical partitions – Customer Feedback for ACE Community Tooling](https://feedback.azure.com/forums/263030-azure-cosmos-db/suggestions/36636625-improve-scaling-down-experience-remove-redundant)