KENTEM TechBlog

建設業のDXを実現するKENTEMの技術ブログです。

CosmosDBのRU消費量についてまとめてみる

ここ一年程、新規製品の開発でAzure CosmosDBに触れることができました。
CosmosDBはMicrosoft Azureで手軽に使用できるNoSQLサービスですが、 いざリリースしてみると消費RUが想定以上となり金額面で苦労するケースがありそうです。 今回はCosmosDBの各処理毎のRU消費についてまとめてみようと思います。

前提

サンプルとして検証したデータは以下のような形式になっています。
階層パーティションとしてParentId、Idが設定されているイメージです。

public class SampleDataModel
{
    public string ParentId{ get; set; }
    public string Id { get; set; }

    public string LinkId{ get; set; }
    public string BaseSettingId { get; set; }
    public SampleDataSetting { get; set; };
    public SampleTextItem TextItem { get; set; };
    public SampleLayoutItem[] LayoutItems{ get; set; } = [];
}

また、取得時は1件のみ取得と375件取得の2種類を計測しています。
( 375という数字に意味はありません(^^; )

ReadItemAsync()で取得した場合

1件取得:1.05 RU
375件取得:393.75 RU

// Sample Code
var pk = new PartitionKeyBuilder()
   .Add(parentId.ToString())
   .Add(id.ToString()).Build();
var res = await _container.ReadItemAsync<SampleDataModel>(id.ToString(), pk);

ReadManyItemAsync()で取得した場合

375件取得:137.05

// Sample Code
var items = ids.Select(id =>
{
    var pk = new PartitionKeyBuilder()
    .Add(parentId.ToString())
    .Add(id.ToString()).Build();

    return (id.ToString(), pk);
});
var res = await _container.ReadManyItemsAsync<SampleDataModel>(items);

GetItemLinqQueryable()で取得した場合

1件取得:2.95 RU
375件取得:22.1 RU

// Sample Code
var requestOpt = new QueryRequestOptions { PartitionKey = new PartitionKey(parentId.ToString()) };
var serializerOpt = new CosmosLinqSerializerOptions { PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase };
using var iterator = _container.GetItemLinqQueryable<SampleDataModel>(
    requestOptions: requestOpt, linqSerializerOptions: serializerOpt)
    .Where(q => q.ParentId == parentId)
    .ToFeedIterator();

var result = new List<SampleDataModel>();
while (iterator.HasMoreResults) {
    var res = await _container.ReadIteratorNextAsync(iterator);
    result.AddRange(res);
}

GetItemLinqQueryable()でデータの一部を取得した場合

375件取得:17.87 RU
※ 必要なデータがピンポイントであればSELECT文を
  使用することでRUを抑える事ができます。

// Sample Code
var requestOpt = new QueryRequestOptions { PartitionKey = new PartitionKey(parentId.ToString()) };
var serializerOpt = new CosmosLinqSerializerOptions { PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase };
using var iterator = _container.GetItemLinqQueryable<SampleDataModel>(
    requestOptions: requestOpt, linqSerializerOptions: serializerOpt)
    .Where(q => q.ParentId == parentId)
    .Select(q => q.LinkId)
    .ToFeedIterator();

var result = new List<string>();
while (iterator.HasMoreResults) {
    var res = await _container.ReadIteratorNextAsync(iterator);
    result.AddRange(res);
}

CreateItemAsync()でのデータ追加した場合

1件追加:35.14 RU

// Sample Code
var pk = new PartitionKeyBuilder()
       .Add(item.ParentId.ToString())
       .Add(item.Id.ToString()).Build();
var result = await _container.CreateItemAsync(item, pk);

ReplaceItemAsync()でデータ更新した場合

1件変更:13.05 RU

// Sample Code
var pk = new PartitionKeyBuilder()
       .Add(item.ParentId.ToString())
       .Add(item.Id.ToString()).Build();
var result = await _container.ReplaceItemAsync(item, item.Id.ToString(), pk);

PatchItemAsync()でデータ更新した場合

1件変更:13.6 RU

// Sample Code
var pk = new PartitionKeyBuilder()
    .Add(parentId.ToString())
    .Add(id.ToString()).Build();
var result = await _container.PatchItemAsync<SampleDataModel>(id.ToString(), pk, patchOperations);

DeleteItemAsync()でデータ削除した場合

1件削除:35.05 RU

// Sample Code
await _container.DeleteItemAsync<SampleDataModel>(strId, new PartitionKey(id.ToString()));

まとめ

扱うデータの構造による影響はあると思いますが、今回のケースでは上記表のような結果となりました。
GetItemLinqQueryable()の複数件取得が妙に消費RUが少ない結果となりましたが、取得結果としては問題ないことを確認しています。

今回の検証をまとめると、、、
① 1件のみのデータ取得はReadItemAsync()がお勧め
② 複数件の場合はGetItemLinqQueryable()の消費RUが少なそう(?)
 ※これについてはもう少し検証したいですね
③ 追加・変更・削除のRU消費は以下の関係で、
  Patchは部分更新だけど処理が軽いわけではない
  Create == Delete >> Replace == Patch

おわりに

KENTEMでは、様々な拠点でエンジニアを大募集しています! 建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。 recruit.kentem.jp career.kentem.jp