LightNode 1.0、或いはWeb APIでのGlimpseの使い方

こないだ、RedisクライアントのCloudStructuresを1.0にしたばかりですが、今回は大昔に作った自作Web APIフレームワークのLightNodeを1.0にしました。なんでドタバタやってるのかというと、.NET XRE(ASP.NET vNext)を様子見してたんですが、そろそろ今年一年どうしていくかの態度を決めなければならなくて、結論としては、OWINで行くことにしたからです。ちゃんちゃん。その辺の理由なんかは後ほど。

さて、Glimpseです。なにはなくともGlimpseです。イマドキでC#でウェブ作るんなら、まずはGlimpse入れましょう。絶対必須です。使ったことないんなら今すぐ使ってください。圧倒的なVisual Profiling!ボトルネックが一目瞭然。コンフィグも一覧されるので、普段気にしていなかったところも丸見え。データアクセスが何やってるかも一発で分かる。ちなみに、競合としては昔あったMiniProfilerは窓から投げ捨てましょう。ASP.NET開発はもはやGlimpse以前と以後で分けられると言っても過言ではない。

で、LightNode 1.0です。変更点はGlimpseにフル対応させたことで、ついでに細かいとこ直しまくりました、と。ともあれGlimpse対応が全てです。

で、作ってる間にGlimpseをWeb API(ASP.NET Web APIとは言ってない)系で使ったり、Owinと合わせて使ったりすることのノウハウも溜まったので、LightNodeの話というかは、そっちのことを放出したいな、というのがこの記事の趣旨ですね!

OwinでGlimpseを使う

Glimpse自体はOwinに対応していません。勿論、vNextへの対応も含めてSystem.Webへの依存を断ち切ろうとしたGlimpse v2の計画は随分前から始まっているんですが、Issueをずっと見ている限り、かなり進捗は悪く、難航しているようです。正直、いつ完了するか全く期待持てない感じで、残念ながら待っていても使えるようにはなりません。

しかし、そもそもGlimpseのシステムはただのHttpModuleとHttpHandlerで動いています。つまり、Microsoft.Owin.Host.SystemWebでホストしている限りは、Owinであろうと関係なく動きます。動くはずです。実際Glimpse.axdにアクセスすれば表示されるし、一見動いています。そしてGlimpseにはページ埋め込みの他、Standaloneでの起動が可能(Glimpse.axdでの右側)なのでそこから起動すると……

いくらOwinでページ作ってアクセスしても何も表示されません、データがHistoryに蓄積されません。これにめっちゃハマって以前は諦めたんですが、今回LightNodeをOwinに何が何でも対応させたくて改めて調べた結果、対策分かりました。原因としては、Glimpseはリクエストの完了をPostReleaseRequestStateで受け止めているんですが、Microsoft.Owin.Host.SystemWebでホストしてOwinによるリクエストハンドリングでは、完了してもPostReleaseRequestStateが呼ばれません。結果的にOwinでふつーにやってる限りではGlimpseでモニタできない。

対策としては、単純に手動でEndRequestを叩いてやればいいでしょう。Middlewareを作るなら

public Task Invoke(IDictionary<string, object> environment)
{
    return next(environment).ContinueWith((_, state) =>
    {
        ((state as HttpContext).Application["__GlimpseRuntime"] as IGlimpseRuntime).EndRequest();
    }, System.Web.HttpContext.Current);
}

ということになります。このMiddlewareを真っ先に有効にしてやれば、全てのOwinパイプラインが完了した際にEndRequestが叩かれる、という構造が出来上がります。System.Webをガッツリ使ったMiddlewareなんて気持ち悪いって?いやいや、まぁいーんですよ、そもそもGlimpseがSystem.Webでしか現状動かないんだから、ガタガタ言うでない。

さて、LightNodeのGlimpse対応DLLにはこのMiddlewareを最初から同梱してあります。LightNodeでGlimpse対応のConfigurationを書く場合は、以下のようになります。

public void Configuration(Owin.IAppBuilder app)
{
    app.EnableGlimpse();
    app.MapWhen(x => !x.Request.Path.Value.StartsWith("/glimpse.axd", StringComparison.OrdinalIgnoreCase), x =>
    {
        x.UseLightNode(new LightNodeOptions()
        {
            OperationCoordinatorFactory = new GlimpseProfilingOperationCoordinatorFactory()
        });
    });
}

まずEnableGlimpse、これが先のEndRequestを手動で叩くものになってます。次にMapWhenで、Glimpse.axdだけOwinパイプラインから外してやることで、LightNodeと共存させられます!ついでに、LigthNodeでのGlimpseモニタリングを有効にする場合はGlimpseProfilingOperationCoordinatorFactoryをOptionに渡してあげれば全部完了。

LightNode+GlimpseによるWeb APIモニタリング

何ができるようになるの?何が嬉しいの?というと、勿論当然まずはTimelineへの表示。

フィルター(Before/After)とメソッド本体がTimeline上で見えるようになります。これは中身何もないですが、勿論DatabaseやRedis、Httpアクセスなどがあれば、それらもGlimpseは全部乗っけることができるし、それらをWeb APIでも見ることができる。圧倒的に捗る。

そしてもう一つがLightNodeタブ。

一回のリクエストのパラメータと、戻り値が表示されます。API開発の辛さって、戻り値が見えない(クライアント側でハンドリングして何か表示したりするも、領域的に見づらかったりする)のが結構あるなーって私は思っていて、それがこのLightNodeタブで解消されます。ちなみにもし例外があった場合は、ちゃんと例外を表示します。

また、ExecutionのPhaseが以降はすべてExceptionになってるので、フィルターが遠ったパスも確認しやすいはずです。

Web APIのためのGlimpseコンフィグ

Web APIのためにGlimpseを使う場合、ふつーのWeb用のコンフィグだと些か不便なところがあるので、調整したほうがいいでしょう。私のお薦めは以下の感じです。

<!-- GlimpseはHUDディスプレイ表示のためなどでレスポンスを書き換えることがありますが、勿論APIには不都合です。
     デフォルトはPersistResults(結果のHistory保存のみ)にしましょう -->
<glimpse defaultRuntimePolicy="PersistResults" endpointBaseUri="~/Glimpse.axd">
    <tabs>
        <ignoredTypes>
            <!-- OWINで使うならこれらは不要でしょう、出てるだけ邪魔なので消します -->
            <add type="Glimpse.AspNet.Tab.Cache, Glimpse.AspNet" />
            <add type="Glimpse.AspNet.Tab.Routes, Glimpse.AspNet" />
            <add type="Glimpse.AspNet.Tab.Session, Glimpse.AspNet" />
        </ignoredTypes>
    </tabs>
    <runtimePolicies>
        <ignoredTypes>
            <!-- クライアントがクッキー使うとは限らないので、無視しましょう、そうしないとHistoryに表示されません -->
            <add type="Glimpse.Core.Policy.ControlCookiePolicy, Glimpse.Core" />
            <!-- 404とかもAPIならハンドリングして表示したい -->
            <add type="Glimpse.Core.Policy.StatusCodePolicy, Glimpse.Core" />
            <!-- Ajaxじゃないなら -->
            <add type="Glimpse.Core.Policy.AjaxPolicy, Glimpse.Core" />
            <!-- リモートで起動(APIならそのほうが多いよね?)でも有効にする -->
            <add type="Glimpse.AspNet.Policy.LocalPolicy, Glimpse.AspNet" />
        </ignoredTypes>
    </runtimePolicies>
</glimpse>

defaultRuntimePolicyと、そして特にControlCookiePolicyが重要です。利用シチュエーションとしてStandalone Glimpseで起動してHistoryから結果を見る、という使い方になってくるはずなので(というかWeb APIだとそうしか方法ないし)、Cookieで選別されても不便すぎるかな、ブラウザからのAjaxならともかくモバイル機器から叩かれてる場合とかね。

さて、それは別として、様々なクライアントからのリクエストが混ざって判別できないというのも、それはそれで不便です。これを区別する手段は、あります。それは、クッキーです(笑) 判別用にクッキーでID振ってやるとわかりやすくていいでしょう。例えば以下の様な感じです。

var req = WebRequest.CreateHttp("http://localhost:41932/Member/Random?seed=13");
// "glimpseid" is Glimpse's client grouping key
req.CookieContainer = new CookieContainer();
req.CookieContainer.Add(new Uri("http://localhost:41932"), new Cookie("glimpseid", "UserId:4"));

glimpseidというのがキーなので、例えばそこにユーザーIDとか振っておくと見分けがついてすごく便利になります。

こんな感じです。これはデバッグビルド時のみといった形で、クライアントサイドで埋め込んであげたいですね。

LightNodeを使う利点

というわけでGlimpseとの連携が超強力なわけですが、LightNode自体はまず言っておくと、誰にでも薦めはしません。この手のフレームワークで何より大事なのが標準に乗っかることです。C#での大正義はASP.NET Web APIです、そこは揺るぎません。その上でLightNodeの利点は「シンプルなAPIがシンプルに作れる」「Glimpseによる強力なデバッグ支援」「クライアントコード自動生成」です。特に非公開のインターナルなWeb API層向けですね。反面お薦めしないのは、RESTfulにこだわりたい人です。LightNodeは設計思想として徹底的にRESTfulを無視してるんで、準拠するつもりは1ミリもありません。例えば、インターナルなAPIでRESTfulのために1つのURIを決めるのに3日議論するとか、凄まじく馬鹿げているわけで。LightNodeは悩みを与えません、そもそもメソッド書くしかできないという制約を与えているから。

凝ったルーティングもアホくさい。インターナルなWeb APIで、モバイル機器からのアクセスを前提にすると、クライアントサイドでのAPIライブラリを書くことになりますが、ルーティングが凝っていれば凝っているほど対応が面倒くさいだけ、という。嬉しさなんて0.1ミリもない。結局、ルールはある程度固定のほうが良いんですよ。さすがにパブリックAPIなら長いものに適当に巻かれて適当に誤魔化しますが。

というわけで、どういう人に薦めるかというと「とりあえずサクッとWeb API作りたい人」「モバイルクライアントからアクセスするインターナルなWeb APIを作りたい人」ですかねー。別にパブリックなのも作れないことはないですけど、別にそこまで違和感あるURLになるわけでもないですしね。

ちなみに、MVCとの共存は可能です。例えば

public void Configuration(IAppBuilder app)
{
    app.Map("/api",  x =>
    {
        x.UseLightNode();
    });
}

といった感じにapi以下をLightNodeのパスってことにすればOK。それ以外のパスではASP.NET MVCが呼ばれます。ルートが変わるだけなので、他のコンフィグは不要です。あんまり細かくゴチャゴチャやると辛いだけなので、このぐらいにしておくのがいいですね。ちなみに、Owinで困るのはHttpModuleとの共存だったりします。実行順序もグチャグチャになるし(一応、少しはOwin側でコントロールかけられますが、辛いしね)同じようなものが複数箇所にあるというのは、普通にイけてない。これはMiddlewareのほうに寄せていきたいところ。脱HttpModule。

まとめ

あ、で、OWINな理由って言ってませんでしたっけ。なんかねー、XREは壮大すぎて危険な香りしかしないんですよ。少なくとも、今年の頭(今)に、今年に使う分のテクノロジーを仕込むには、賭けられないレベルで危なっかしい。Previewで遊びながら生暖かく見守るぐらいがちょうどいいです。まあ、アタリマエだろっていえばアタリマエ(ベータすら出てないものを実運用前提で使い出すとかマジキチである)ですけどね、だから別にXREがダメとかそういう話じゃないですよ。むしろXREはまだ評価できる段階ですらないし。

で、今は過渡期で宙ぶらりんなのが凄く困る話で、そのブリッジとしてOWINはアリかな、と。OWIN自体の未来は、まぁASP.NET 5はどうしてOWIN上に乗らなかったのかにあるように、Deadでしょう。しかし、今から来年の分(XREが実用になった世代)を仕込むには、System.Webへの依存の切り離しや、Owin的なパイプラインシステムへの適用は間違いなく重要。OWINならコーディングのノリもASP.NET 5と変わらないしコードの修正での移行も容易になる、最悪互換レイヤーを挟んで適用できるので、「今」の選択としては、消極的にアリです。

ASP.NET Web APIは、うーん、ASP.NET MVCとの統合が見えてる今、改めて選びたくない感半端ないんだよねぇ。GlimpseはASP.NET Web API対応しないの?というと、そういう話もあるにはあったようですが、色々難航していて、PullRequestで物凄く時間かけて(70レス以上!一年近く!)、それでも結局取り込まれてないんですよ。ここまで来るともはやGlimpse v2でのvNext対応でMVC統合されてるんだからそれでいいじゃん、に落ち着きそうで、恐らくもう動きはないでしょう。とか、そういう周辺のエコシステムの動きも今のASP.NET Web APIは鈍化させる状況にあるわけで、あんまポジティブにはなれないなぁ。とはいえ、現状のスタンダードなWeb API構築フレームワークとして消極的にアリ、と言わざるをえないけれど。ちなみにNancyは個人的には全くナシです、あれのどこがいいのかさっぱりわからない。

Glimpseの拡張は、ちょうど社内用拡張も全部書き換えたりして、ここ数日でめちゃくちゃ書きまくったんで、完全に極めた!うぉぉぉぉ、というわけで拡張ガイダンスはいつかそのうち書くかもしれませんし、多分書きません。つーかGlimpseちゃんと日本の世の中で使われてます?大丈夫かなー、さすがにGlimpseは圧倒的に良いので標準レベルで使われなければならないと思うのですけれど。

あー、で、LightNodeは、まあ良く出来てますよ、用途の絞り方というか課題設定が明確で、実装もきっちりしてありますし。うん、私は好きですけど(そりゃそうだ)、人に薦めるかといったら、Microsoftの方針がOwin的なオープンの流れから、やっぱり大Microsoft的なところに一瞬で戻ったりしてるんで(Hanselmanには少し幻滅している)、まぁ長いものには巻かれておきましょう。

CloudStructures 1.0 - StackExchange.Redis対応、RedisInfoタブ(Glimpse)

CloudStructures、というRedisライブラリを以前に作ってたわけなのですが(CloudStructures - ローカルとクラウドのデータ構造を透過的に表現するC# + Redisライブラリ)、2013年末にGlimpseプラグインを追加してから一切音沙汰がなかった。私お得意の作るだけ作って放置パターンか!と思いきや、ここにきて突然の大更新。APIも破壊的大変更祭り。バージョンもどどーんと上げて1.0。ほぅ……。

一番大きいのが、ベースにしてるライブラリがBookSleeveからStackExchange.Redisになりました。StackExchange.RedisはBookSleeveの後継で、そしてAzure Redis Cacheのドキュメントでもマニュアルに使用されているなど、今どきの.NETにおけるRedisクライアントのデファクトの位置にあると言ってよいでしょう。当然、移行する必要があったんですが腰が重くて……。

APIを変えた理由は、以前は「ローカルとクラウドのデータ構造を透過的に表現」というのに拘ってIAsyncCollection的に見せるのに気を配ってたんですが、Redis本来のコマンド表現と乖離があって、かなり使いづらかったんでやめたほうがいーな、と。メソッド名が変わっただけで使い方は一緒なんですが、とりあえずRedisのコマンド名が露出するようになりました。ま、このほうが全然イイですね。抽象化なんて幻想だわさ、特に名前の。

CloudStructuresの必要性

こんな得体のしれない野良ライブラリなんて使いたくねーよ、StackExchange.Redisを生で使えばいいじゃん。と、思う気持ちは至極当然でまっとうな感覚だと思います。私もそう思う。そして、単純なStringGet/Setぐらいしか使わないならそれでOKです、本当にただのキャッシュストアとして使うだけならば。しかし、本気でRedisを使い倒す、本気でRedisの様々なデータ構造を活用していこうとすると、StackExchange.Redisを生で使うのは限界が来ます。戻り値のオブジェクトへのマッピングすらないので、そこら中にSeiralize/Deserializeしなければならなくなる。ADO.NETのDbDataReaderを生で使うようなもので、そうなったら普通はなんかラップするよね?ADO.NETにはDapperのようなMicro ORMからEntity FrameworkのようなフルセットのORMまである。StackExchange.Redisが生ADO.NETを志向するならば(これは作者も言明していて、付随機能は足さない方針のようです)ならば、そこにO/R(Object/Redis)マッパーが必要なのは自然のことで、それがCloudStructuresです。

CloudStructuresが提供するのは自動シリアライズ/デシリアライズ、キーからの分散コネクション(シャーディング)、コマンドのロギング、Web.configからの接続管理、そしてGlimpse用の各種可視化プロファイラーです。元々、というか今もCloudStructuresはうちの会社でかなりヘヴィに使ってて(このことは何度か記事でも推してます、技評のグラニがC#にこだわる理由とか)、コマンドのロギングとかは執拗に拘ってます。今回はそうした長い利用経験から、やっぱイケてない部分も沢山あったので徹底的に見直しました。

シャーディングは、StackExchange.RedisはそもそもConnectionMultiplexerという形で内部で複数の台への接続を抱えられるんですが、これはどちらかというと障害耐性的な機能(Master/Slaveや障害検知時の自動昇格など)が主なので、Memcached的なクライアントサイドでの分散はBookSleeveの時と変わらず持っていません。なので引き続きシャーディングはCloudStructures側の機能として提供しています。

そもそもRedisが必要かどうかだと、んー、私としては規模に関わらず絶対に入れたほうがいいと思ってます。RDBMSの不得意なところを綺麗に補完できるので、RDBMSだけで頑張るよりも、ちょっとしたとこに使ってやると物凄く楽になると思います。導入もAzure Redis CacheやAWSのElastiCache for Redisのようにマネージドのキャッシュサービスが用意されているので、特にクラウド環境ならば簡単に導入できますしね。

使い方の基本

RedisSettingsまたはRedisGroupを保持して、各データ構造用のクラスをキー付きで作って、メソッド(全部async)を呼ぶ、です。

// 設定はスタティックに保持しといてください
public static class RedisServer
{
    public static readonly RedisSettings Default = new RedisSettings("127.0.0.1");
}

// こんなクラスがあるとして
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// RedisStringという型を作って... (RedisSettings.Default.String<Person>("key")でも作れます)
var redis = new RedisString<Person>(RedisServer.Default, "test-string-key");

// コマンドをがしがし呼ぶ、何があるかはIntelliSenseで分かる
await redis.Set(new Person { Name = "John", Age = 34 });

// 取得もそのまま呼ぶ 
var copy = await redis.Get();

// Listも同じ感じ
var list = new RedisList<Person>(RedisServer.Default, "test-list-key");
await list.LeftPush(new[] { new Person { Name = "Tom" }, new Person { Name = "Mary" } });

var persons = await list.Range(0, 10);

難しいところはなく、割と直感的。StackExchange.Redisのままだと、特にListが辛かったりするんで、相当楽になれるかな。

メソッド名は基本的にStackExchange.Redisのメソッド名からデータ構造名のプリフィックスとAsyncサフィックスを抜いたものになってます。例えばSetAddAsync()はRedisSet.Add()になります。SetAddなんて最悪ですからね、そのまんま扱いたくない名前。Asyncをつけるかどうかはちょっと悩ましかったんですが、まぁ全部Asyncだしいっか、と思ったんで抜いちゃいました。

他の特徴としては全てのセット系のメソッドにRedisExpiryという引数を足してます。これは、SetのついでにExpireを足すって奴ですね。標準だとStringSetぐらいにしかないんですが、元々Redisは個別にExpireを呼べば自由につけれるので、自動でセットでつけてくれるような仕組みにしました。なんだかんだでExpireはつける必要があったりして、今までは毎回Task.WhenAllでまとめててたりしてたんですがすっごく面倒だったので、これで相当楽になれる、かな?

RedisExpiryはTimeSpanかDateTimeから暗黙的変換で生成されるので、明示的に作る必要はありません。

var list = new RedisList<int>(settings);
await list.LeftPush(1, expiry: TimeSpan.FromSeconds(30));
await list.LeftPush(10, expiry: DateTime.Now.AddDays(1));

こんな感じ。これは、StackExchange.RedisがRedisKey(stringかbyte[]から暗黙的に変換可能)やRedisValue(基本型に暗黙的/明示的に変換可能)な仕組みなので、それに似せてみました。違和感なく繋がるのではないかな。

コンフィグ

Web.configかapp.cofingから設定情報を引っ張ってこれます。トランスフォームとかもあるので、なんのかんのでWeb.configは重宝しますからねー、あると嬉しいんじゃないでしょうか。

<configSections>
    <section name="cloudStructures" type="CloudStructures.CloudStructuresConfigurationSection, CloudStructures" />
</configSections>

<cloudStructures>
    <redis>
        <group name="Cache">
            <!-- Simple Grouping(key sharding) -->
            <add connectionString="127.0.0.1,allowAdmin=true" db="0" />
            <add connectionString="127.0.0.1,allowAdmin=true" db="1" />
        </group>
        <group name="Session">
            <!-- Full option -->
            <add connectionString="127.0.0.1,allowAdmin=true" db="2" valueConverter="CloudStructures.GZipJsonRedisValueConverter, CloudStructures"  commandTracer="Glimpse.CloudStructures.Redis.GlimpseRedisCommandTracer, Glimpse.CloudStructures.Redis" />
        </group>
    </redis>
</cloudStructures>

こんな感じに定義して、

public static class RedisGroups
{
    // load from web.config
    static Dictionary<string, RedisGroup> configDict = CloudStructures.CloudStructuresConfigurationSection
        .GetSection()
        .ToRedisGroups()
        .ToDictionary(x => x.GroupName);

    // setup group
    public static readonly RedisGroup Cache = configDict["Cache"];
    public static readonly RedisGroup Session = configDict["Session"];

    static RedisGroups()
    {
        // 後述しますがGlimpseのRedisInfoを有効にする場合はここで登録する
        Glimpse.CloudStructures.Redis.RedisInfoTab.RegisiterConnection(new[] { Cache, Session });
    }
}

こんな風にstatic変数に詰めてやると楽に扱えます。

シリアライズ

CloudStructuresは基本型(int, double, string, etc)はそのまま格納し、オブジェクトはシリアライザを通します。シリアライザとして標準ではJSONシリアライザのJilを使っています。理由は、速いから。JilはJSON.NETと違って、JsonReader/Writerも提供しないし、複雑なカスタムオプションもフォールバックもありません(多少の(特にDateTime周りの)オプションはありますが)。単純にJSONをシリアライズ/デシリアライズする、もしくはdynamicで受け取る。それだけです。まぁ、CloudStructuresの用途には全然合ってる。

以前はprotobuf-netを使っていたんですが、今後はやめようと思ってます。理由は、DataMemberをつけて回るのが面倒だから、ではなくて、空配列/空文字列/nullのハンドリングが凄く大変だったり(ネストしたオブジェクトの空配列がデシリアライズしたらnullになってた、とかね……これは正直ヤバすぎた)、バージョニング(特にEnumの!)が辛かったり、型がないとデシリアライズできないのでちょっとしたDumpすらできなかったりと、実運用上クリティカルすぎる案件が多くてそろそろもう無理。

かわりに、ではないですが圧縮することを提案します。CloudStructuresは標準でGZipJsonRedisValueConverterというものも用意していまして、それに差し替えることでJSONをGZipで圧縮して格納/展開します。圧縮は、特にデカい配列を突っ込んだりするときに物凄く効きます。めちゃくちゃ容量縮みます。protobufにせよmsgpackにせよ、シリアライザは圧縮、ではないんで、バイナリフォーマットとして小さくはなっても、配列にたいしてめちゃくちゃ縮むとかそういうことは起こり得ません(勿論、別にmsgpack+GZipとか併用するのは構わないけれど)。

圧縮の欠点は圧縮なんで、圧縮/解凍にそれなりにパフォーマンスを取られること。と、いうわけでCloudStructuresではLZ4で圧縮するものも用意しました。LZ4はfastest compression algorithmということで、GZipと比べて数倍、圧縮/解凍が速い、です(ただしサイズ自体はGZipよりは縮まない)。この手の用途ではかなり適しているかなー、と。LZ4のライブラリはLZ4 for .NETを用いてます。

インストールはNuGetから入れてもらった後に、LZ4JsonRedisValueConverterに差し替えるだけ。

ふつーはそのまま生JSON、気にしたいけど色々入れたくない人はGZip、エクストリームに頑張ってみたい人はLZ4を選べばいいと思います。更にもっとやりたい人はObjectRedisValueConverterBaseを継承して、自作のRedisValueConverterを作ってみてくださいな。

Glimpseプラグイン

もはやASP.NET開発でGlimpseは絶対に欠かせません。使わないのはありえないレベル。あ、MiniProfilerはもういらないので投げ捨てましょう。というわけでCloudStructuresはGlimpse用のプラグインをしっかり用意してあります。相当気合入れて作りこんであるので、これのためにもRedis使うならCloudStructuresで触るべき、と言えます。マジで。

インストールはNuGetから本体とは別に。それとGlimpseを使う場合は、commandTracerにGlimpseRedisCommandTracerを渡しておいてあげてください。またRedisInfoで情報を出す場合、接続文字列でallowAdminをtrueにしておく必要があります。

<add connectionString="127.0.0.1,allowAdmin=true" db="0" commandTracer="Glimpse.CloudStructures.Redis.GlimpseRedisCommandTracer, Glimpse.CloudStructures.Redis" />

まず、Timeline。

コマンドの並列実行具合がしっかりタイムラインで確認できます。

Redisタブ。

コマンド名、キー名、送受信オブジェクトのダンプとサイズ、Expire時間と処理にかかった時間、そしてキーとコマンドで重複して発行してたら警告。これを見れば一回のページリクエストの中でどうRedisを使ったかが完全に分かるようになってます。不足してる情報は一切なし、とにかく全部出せる仕組みにしました。

最後にRedisInfoタブ。RedisInfoタブを使うには、最初に言ったallowAdmin=trueにすることと、もう一つ、最初に情報表示に使うRedisGroupを登録しておく必要があります。

Glimpse.CloudStructures.Redis.RedisInfoTab.RegisiterConnection(new[] { Cache, Session });

ServerInfoからCmdStat、コンフィグ、クライアント側のコンフィグやコネクションの状態を全部表示します。全部。全部。出せそうな情報は全部収集してきてます。こういうの何気に結構地味に相当大事だったりしますのよ、特にRedisサーバーの情報やコンフィグなんて普段は見ないですからね、こうして超絶カジュアルに見れるっての、かなりありがたい。

まとめ

そんなわけで凄く良くなったんで、かなりお薦めデス。ネーミングが直球なものしか付けないことの多い私にしては、CloudStructuresってライブラリ名はかなりカッコイイという点でもお薦めですね!

問題は旧CloudStructuresとの互換性が、かなり無いので既に使ってる場合は移行が大変ってことデスネ。うちはどうしたんだって?移行してないよ!どーしようかなぁ、うーん、そこはちょっとかなり悩ましい……。

まぁCloudStructuresを使うかどうかはともかくとして、RedisはC#界隈でももっとばんばん使われて欲すぃですねー、そして使うならStringGet/Setだけじゃもったいない。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(.NET)
April 2011
|
July 2025

X:@neuecc GitHub:neuecc

Archive