しばやん雑記

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

Cosmos DB を利用する上で最初にはまった部分のメモ

Twitter でおーみさんに Cosmos DB についていろいろ聞いたので、忘れないようにメモっておきます。kyrt.in に書いてあればみんな幸せになりそうなんですが、いつ書いてくれるかわからないので。

HTTP GW ではなく TCP Direct を使う

デフォルト設定のまま Cosmos DB を使っていると、どうも遅いような気がしたので聞いたところ、TCP Direct を使うように設定を変えるのが良いと教えて貰いました。

DocumentClient を作るときに ConnectionPolicy を渡してやればよいです。

var connectionPolicy = new ConnectionPolicy
{
    ConnectionMode = ConnectionMode.Direct,
    ConnectionProtocol = Protocol.Tcp
};

var documentClient = new DocumentClient(new Uri("ENDPOINT"), "PRIMARYKEY", connectionPolicy);

これで TCP Direct モードが使われるようになるようです。デフォルトを TCP にしておいて欲しいですね。

DocumentClient はシングルトンに

TCP Direct にしてもまだ遅いような気がしたので、更におーみさんに聞いてみました。DocumentClient は TCP Direct の場合はコネクションをプールするらしいので、シングルトン推奨のようです。

確かに毎回接続を作り直して、初期情報を受信してたら遅いよねという感じです。

幸いにも今回は ASP.NET Core アプリケーションで Cosmos DB を使っていたので、DI を使って簡単にシングルトンを実現出来ました。Factory を使って実現します。

services.AddSingleton(provider =>
{
    var connectionPolicy = new ConnectionPolicy
    {
        ConnectionMode = ConnectionMode.Direct,
        ConnectionProtocol = Protocol.Tcp
    };

    return new DocumentClient(new Uri("ENDPOINT"), "PRIMARYKEY", connectionPolicy);
});

使う場合にはコンストラクタで DocumentClient を受け取るようにするだけです。

public class DemoService
{
    public DemoService(DocumentClient documentClient)
    {
        // documentClient は DI で解決される
    }
}

これでアプリケーションで 1 つの DocumentClient を利用することが出来ます。慣れてくると ASP.NET Core の DI は結構使いやすく感じるようになってきました。

Optimistic Concurrency は ETag を使う

データを扱う上で大体問題になってくるのがロックとかなんですが、Cosmos DB には当然ながらロックとか用意されていないので、必要な場合は Optimistic Concurrency を使って制御を行うことになります。

適当に調べた感じではヒットしなかったので、おーみさんに聞いて ETag を教えて貰いました。

Cosmos DB のドキュメントには全て ETag が含まれていて、Replace 時に AccessCondition に ETag を設定しておくと、一致した時のみ処理を実行するように出来るみたいです。考え方は HTTP 的ですね。

try
{
    var condition = new AccessCondition
    {
        Condition = document.etag,
        Type = AccessConditionType.IfMatch
    };

    await Client.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, document.id), document, new RequestOptions { AccessCondition = condition });
}
catch (DocumentClientException ex) when (ex.StatusCode == HttpStatusCode.PreconditionFailed)
{
    // optimistic concurrency で失敗
}

処理に失敗した時には PreconditionFailed がステータスコードとして返ってくるので、何かしら処理が必要な場合は行います。新しく追加された catch ~ when は地味に便利でした。

今回のアプリケーションでは Repository Pattern を使って処理をまとめていたので、全ての更新処理を対象に Optimistic Concurrency を入れることが容易でした。

暇になった時にでも、どのように Cosmos DB への処理部分を書いたかまとめたい気がします。