Cosmos DBのドキュメント部分更新

経緯

以前、受託プロジェクトで、CosmosDBにポイントデータを入れて管理する地図アプリを開発していました。詳しい仕様の話は控えますが、そのアプリには次のような機能がありました。

  • ポイントデータに対し、ユーザは評価をすることができる (※「いいね!」機能のようなもの)
  • ユーザから評価された数をカウントして表示する

このアプリのデータベース設計は、CosmosDBの1ドキュメントで1つのポイントの情報を管理するような構成となっていたため、ポイントのドキュメントの中に評価数の属性を持たせ、ユーザの操作によって、評価数をカウントアップしていくことでこの機能を実現しようとしました。

CosmosDBを更新する処理を実装してみた (修正前)

いつもどおり、.NET CoreでWebAPIを実装することにしました。CosmosDBのドキュメントを更新するAPIは次のように実装しました。

private Microsoft.Azure.Cosmos.Container _container;
private Microsoft.Azure.Cosmos.PartitionKey _partitionKey;

private async Task<bool> UpdateFeatureProperty(string id, string propertyName, bool countUp)
{
    // CosmosDBから指定したidのドキュメントを取得します。
    FeatureDocument featureDoc = null;
    var query = new QueryDefinition($"SELECT * FROM c WHERE c.id = '{id}'");
    using (var it = _container.GetItemQueryIterator<FeatureDocument>(query, null, 
        new QueryRequestOptions() { PartitionKey = _partitionKey }))
    {
        while (it.HasMoreResults)
        {
            var currentResultSet = await it.ReadNextAsync();
            foreach (var doc in currentResultSet)
            {
                featureDoc = doc;
                break;
            }
        }
    }
    if (featureDoc != null)
    {
        // 属性値をインクリメントもしくはデクリメントします。
        var nextValue = Convert.ToInt32(featureDoc.Properties[propertyName]) + (countUp ? 1 : -1);
        featureDoc.Properties[propertyName] = nextValue;        
        
        // ドキュメントを更新します。
        var response = await _container.UpsertItemAsync(featureDoc, _partitionKey);
        return response.StatusCode == HttpStatusCode.OK;
    }
    return false;
}

手順は大きく2段階、
1. まず、GetItemQueryでドキュメントのIDを指定してドキュメント全体を取得します。
2. 次に、取得したドキュメントの内容を変更し、UpsertItemでCosmosDBのドキュメントを更新します。

以前はこの方法でドキュメントを更新するしかなかったようですが、昨年のMicrosoft Build 2021で、よりシンプルなインタフェースとして、部分ドキュメント更新機能が公開ました。私もこの機能があることを後から教えて頂き、修正することにしました。

Patch APIに変更

Microsoft公式のドキュメントによると、部分更新の操作は複数定義されているようです。今回作成したアプリでやりたいのは「いいね!」のカウントアップなので、”Increment”を利用します。

修正したソースコードは以下の様になりました。

private async Task<bool> UpdateFeatureProperty(string id, string propertyName, bool countUp)
{
    // 属性値をインクリメントするか、デクリメントするか
    var incrementValue = countUp ? 1 : -1;
    // 部分更新を実行します。
    var respose = await _container.PatchItemAsync<FeatureDocument>(id, _partitionKey, 
        new[] { PatchOperation.Increment($"/properties/{propertyName}", incrementValue) });

    return response.StatusCode == HttpStatusCode.OK;
}

コード量が大幅に削減され、処理の流れもシンプルになりました。全体の処理時間も短縮され、アプリが少しだけ高速に動作するようになりました。CosmosDBへのアクセスも、参照→更新の2回から、Patch処理の1回に削減されることで、CosmosDBに対し消費される要求ユニット(RU)が半減し、運用コストも低く抑えられます。これは使わない手はないですね。

所感

新しく導入されたPatch API (といってもサポート開始は1年近くも前ですが)、従来の実装方法に比べメリットしかないので、使える場合は積極的に使っちゃいましょう。

記事が面白かった方、参考になった方は、是非「イイね」お願いします。

コメントを残す

メールアドレスが公開されることはありません。

CAPTCHA