CosmosDB BulkExecutorの性能をPaaSで試してみた

Build2018のCosmosDB関連アナウンスでは、デモ映えするMulti-Masterネタで盛り上がっていましたが、個人的にはIntroducing the #Azure #CosmosDB Bulk Executor library | Blog | Microsoft Azureが最も気になっていました。アナウンスから少し時間が経ってしまいましたが、ようやくある程度の検証ができたのでメモに残しておきます。

Azure Cosmos DB Bulk Executor

これまで、標準のSDKでは一括挿入や更新がサポートされておらず、頑張ってループ系の処理を書いたり、キューを使った並列処理を自作するしかありませんでした。なので、せっかく高速な処理が売りのCosmosDBを使っていても、スループットを使い切れないというケースも発生していました。

この問題を解決するために登場してきたのがBulkExecutorで、ざっくりいうとCosmosDBへの一括挿入・更新ライブラリです。詳細は公式ドキュメント Azure Cosmos DB BulkExecutor ライブラリの概要 を読みましょう。

GitHub - Azure/azure-cosmosdb-bulkexecutor-dotnet-getting-started のREADMEによると、約9倍の性能向上がみられるとのことです。しかし、サンプルの環境は DS16 v3 Azure VM を使った並列処理との比較で、かつCosmosDB側も100万RUという現実離れした設定だったので、 フツーの規模でPaaSを使うとどうなるのか、「PaaSがかり」としての責任を感じたので 、いろいろ試してみることにしました。

検証環境

実はまず最初に検証した環境がAzure Functionsだったのですが、なんとライブラリのバージョン競合という基本的な問題があることがわかり、やむを得ず普通のWebAppで検証せざるを得ない事態に。まあ、どちらもApp Serviceなので大差はないと思いますが、この問題には結構時間を使いました・・。結局、今回使った環境は以下のような感じです。

  • WebApp(AppService)のS1インスタンス
  • CosmosDBは5,000RUのコレクションでシングルパーティション
  • 1,000ドキュメントのINSERT

ちなみにAzure Functionsのライブラリ問題というのは、Functions SDKがNewtonsoft.Jsonが9.0.1で固定されていることに起因するものです。Bulk Executorライブラリでは10.0.2以上が指定されているため、共存させようとするとまずビルドエラーが発生し、明示的にJSONライブラリを入れて回避してもバインディング周りで問題が出てしまいました(例: CosmosDB Triggerと併存できない等)。残念ですが当面はFunctionsでのBulk Executor利用は避けておいた方が良いと思います。ちなみに関連のIssueも上がっています。

検証コード

比較対象となる通常のINSERTはこんな感じで書きました。asyncなのである程度並列に処理されるはずですが、基本的には CreateDocumentAsync() が繰り返し実行されています。

var task = await Task.WhenAll(
documentsToImportInBatch.Select(
async document => await _client.CreateDocumentAsync(
UriFactory.CreateDocumentCollectionUri(DatabaseName, CollectionName), document)
)
);

BulkExecutorライブラリを利用すると一括INSERTは以下のように BulkImportAsync() を一度実行するだけです。事前にbulkExecutorドライバの生成が必要ですが大したことはありません(その辺は公式サンプルを参照ください)。なお、公式ドキュメントによると内部的には「AIMD-style congestion control」というメカニズムで並列処理をいい感じに実行しているらしいです。なにか凄そうですが良くわかりません。。。

bulkImportResponse = await bulkExecutor.BulkImportAsync(
documents: documentsToImportInBatch,
enableUpsert: false,
disableAutomaticIdGeneration: true,
maxConcurrencyPerPartitionKeyRange: null,
maxInMemorySortingBatchSize: null,
cancellationToken: token
);

検証結果

処理時間(秒) 消費RU/s
繰り返しINSERT 9.2046769 sec 480 RU/s
BulkExecutor利用 1.0882003 sec 4,404 RU/s

なんとPaaS環境でも約9倍の性能向上がみられました!RUもきっちり使い切ってます。設定した5,000RUギリギリを使っているので、もしかすると内部でスロットリングが発生していたかもしれませんが、BulkExecutorはいい感じにリトライも行う仕組みが入っているので(調整も可能)、その点も安心感があります。

BulkExecutorの利用用途は大量データの移行や一時的な作業用途だけでなく、普段使いできる価値が十分ありそうです(ちょっと更新の多めなWebアプリやAPIなど)。CosmosDBに対して何らかの一括挿入を行っているアプリケーションではチューニングの効果絶大の可能性があります。

1つ気をつけることとしては、INSERT時の効率が良くなることでRUの消費が大きくなるため、設定するRUの見直しが必要になる点です。これは性能検証をして適正値を確かめた方が良いでしょう。

あとはFunctionsでもライブラリ競合なく使えるようになってくれて、.NET Coreにも対応してくれれば言うこと無しです。.NET Core対応についてはFeedbackをあげたので、気が向いたらVoteをお願いします!