しばやん雑記

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

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 への処理部分を書いたかまとめたい気がします。