Twitter でおーみさんに Cosmos DB についていろいろ聞いたので、忘れないようにメモっておきます。kyrt.in に書いてあればみんな幸せになりそうなんですが、いつ書いてくれるかわからないので。
HTTP GW ではなく TCP Direct を使う
デフォルト設定のまま Cosmos DB を使っていると、どうも遅いような気がしたので聞いたところ、TCP Direct を使うように設定を変えるのが良いと教えて貰いました。
デフォルトって、HTTP GW だっけ。TCP DIRECTがお勧めされている。実際、2から3割はレイテンシが小さいと思う。
— OMI Takekazu (@takekazuomi) 2017年10月9日
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 の場合はコネクションをプールするらしいので、シングルトン推奨のようです。
DocumentClientはシングルトンが推薦されてる。TCP DIRECTの場合は、コネクションがマルチプレックスで利用され、作り直すとコネクションを貼り直すので遅くなる。しかも、初期接続ではサーバーからパーティション情報の取得等ラウンドトリップが3(?)回ほど発生する
— OMI Takekazu (@takekazuomi) 2017年10月9日
というわけで、毎回DocumentClientを作成すると大分遅くなる。
— OMI Takekazu (@takekazuomi) 2017年10月9日
確かに毎回接続を作り直して、初期情報を受信していたら遅いよねという感じです。
幸いにも今回は 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 を教えて貰いました。
このあたりかな、ETag使ったoptimistic concurrencyのコード例https://t.co/HKNjvW4Fwp
— OMI Takekazu (@takekazuomi) 2017年10月9日
ドキュメントというか、触れられてるのは、このあたり。https://t.co/ZDNm15CaJr
まとまってないけど
ETag If-Match をラップした、AccessCondition なんてクラスがあったりする。https://t.co/oxRtGj7xIx
— OMI Takekazu (@takekazuomi) 2017年10月9日
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 への処理部分を書いたかまとめたい気がします。