しばやん雑記

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

Application Insights に Cosmos DB で消費された RUs を送信すると非常に捗った話

仕事で Cosmos DB を使ってアプリケーションを書きましたが、最近はあらかじめ割り当てておいた RU を突き抜けることがあって原因の調査を行っていました。

その時に Cosmos DB のメトリックだけではコレクション別でしか RU を確認出来ず、Application Insights では処理時間しか取得されておらず不便だったので、自前で消費した RU を送信するようにしました。

RU を送信する処理は Repository のベースクラスに仕込んだので、少しの修正だけで済みました。

上で挙げたサンプルクラスに以下のような処理を追加して、適当なタイミングで呼び出しているだけです。RU 以外にも送っても良い気がしますが、今回は RU だけで十分でした。

TelemetryClient はサンプルなので DI を使って直接渡しましたが、適当にインターフェースを用意した方が Application Insights への依存関係を含めずに済むのでスマートかもしれません。

protected async Task<IList<T>> ExecuteQueryAsync<T>(IDocumentQuery<T> documentQuery, [CallerMemberName] string methodName = null)
{
    var requestCharge = 0.0;
    var list = new List<T>();

    while (documentQuery.HasMoreResults)
    {
        var response = await documentQuery.ExecuteNextAsync<T>();

        requestCharge += response.RequestCharge;

        list.AddRange(response);
    }

    TrackRequestCharge(requestCharge, methodName);

    return list;
}

private void TrackRequestCharge(double requestCharge, [CallerMemberName] string methodName = null)
{
    Telemetry.TrackEvent($"Executed operation {CollectionId}.{methodName} in {requestCharge} RUs", new Dictionary<string, string>
        {
            { "Collection", CollectionId }
        },
        new Dictionary<string, double>
        {
            { "Request units", requestCharge }
        });
}

ExecuteQueryAsync に関しては前回用意してなかったですが、モリス先輩がタイミングよく書いていたので参考にして組み込みました。

複数回 ExecuteNextAsync を呼び出す可能性があるので RU は集計するようにしてます。

そんなこんなでアプリケーションを実行してみると、カスタムイベントとして Application Insights に Cosmos DB で消費された RUs と実行したメソッド情報が表示されます。

サンプルなのでデータの偏りがなく、RU が一定になっているのでありがたみは感じないかもしれません。

f:id:shiba-yan:20171204230014p:plain

しかし実際のアプリケーションでの調査では、このカスタムイベントと Application Insights の Session Timeline が非常に強力でした。処理の流れが時系列で簡単に表示できる、最高に素晴らしい機能です。

組み合わせることで、どのページからの呼び出しで RU を過剰に消費しているか一目で確認出来ました。

f:id:shiba-yan:20171204230728p:plain

ちなみに、カスタムプロパティとしてコレクション名を送信しているので、Metrics Explorer から簡単にコレクション単位での RU 消費をグラフにすることが出来ます。

f:id:shiba-yan:20171204224345p:plain

グルーピングを設定しないと、関係なく集計してしまってあまり意味がありません。

実際に設定すると、以下のような表示になります。一目で状況を把握することが出来ますね。

f:id:shiba-yan:20171204225749p:plain

Cosmos DB は RU の消費状態を把握し、最適化を行うかが重要だと再認識しました。そのためには組み込みのメトリックだけでは不十分で、APM と上手く組み合わせると幸せになれるという話です。

実際に仕事で作ったアプリケーションで発生していた RU の過剰消費は一瞬で解決しました。それは余計なデータまで読みに行くという、単純なバグだったというオチでした。