銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

ISUCON9の参考実装をC# (ASP.NET Core 3.1)に書き換えてみた

ISUCONは説明不要の一大イベントだと思います。簡単にいうと、お題に従いより高速なWebアプリケーションを構築するコンテストです。昨年行われたISUCON9の本戦問題についてはこちらに記事があります。

isucon.net

さて、このISUCON9、参加者は予め用意されている参考実装を使うことができます。これを元に書き換えることもできますし、参考に留めて自分で一から書くこともできます。

github.com

参考実装はいくつかの言語で実装されています。Go, Perl, Python, Ruby, PHP。そう、C#がないのです。ASP.NET Coreも3.1になりましたし、必要な機能も揃ってるだろうと思い、C#に書き直してみました。

github.com

動作確認はMacで行っています。ちなみにベンチマーカーを完走させるのに必要なインデックスを貼っただけの状態での初期スコアは 約2200点です。

f:id:tanaka733:20200306195458p:plain

なお、書き直しにあたっては、ASP.NET Core 3.1 Web APIを採用し、データベースアクセスにはDapperを使っています。主にGo言語の参考実装を参考にしましたが、テストケースを回したわけではないので実装ミスがきっとあると思われます。ステータスコードやレスポンス形式は完全に同一になるように実装した(つもり)です。Go言語版の参考実装では極力1ファイルにまとめているようでしたが、流石にASP.NET Coreで1ファイルに全てのクラスをまとめてるのはあまり見かけないので、クラスをファイルごとに分けています。また、.NET Coreのコンテナ開発ではあまりやらないと思いますが、ホスト側のコード変更が即コンテナ側に反映されるようにホストパスをマウントしdotent watchでアプリを起動しています。

さて、この書き直しにあたってASP.NET Coreのいろいろな機能を活用したのですが*1、これについて4/3開催予定のC# Tokyoのオンラインmeetupで話す予定です。興味のある方はぜひこちらからご参加ください。

csharp-tokyo.connpass.com

*1:最近公開しているASP.NET Coreの記事はこれ関連だったりする

.NET Standard の HttpClientで送信時のBodyをgzip圧縮したい

HttpClientでPOSTやPUTのような送信を行う場合に、Bodyで送信するコンテンツをGZip圧縮したいと思ったのですが、意外とすぐに使えるサンプルコードが見当たりませんでした。

docs.microsoft.com

とりあえず動くコードは書けたので、メモしておきたいと思います。Bodyで送信するコンテンツは、抽象クラスであるHttpContentで扱うのですが、単純な文字列の場合はStringContent、ストリームの場合はStreamContentなど用意されている具象クラスを利用できることがあります。

docs.microsoft.com

docs.microsoft.com

が、GZip圧縮できるクラスはないようなので、自分でHttpContentを継承する必要がありそうです。主に必要なのは、SerializeToStreamAsyncで引数のStreamにGZip圧縮したコンテンツを書き込む処理です。また、実装する際には送信するコンテンツをコンストラクタなどで受け取る必要があります。今回送信するコンテンツは文字列結合で生成しているので、ZStringのUtf8ValueStringBuilderで渡してストリームに書き込むことにしました。

github.com

というわけでこのように実装しました。

using Cysharp.Text;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

public class GZipZStringContent : HttpContent
{
    private readonly Utf8ValueStringBuilder sb;

    public GZipZStringContent(Utf8ValueStringBuilder sb)
    {
        Headers.TryAddWithoutValidation("Content-Type", "application/gzip");
        Headers.ContentEncoding.Add("gzip");
        this.sb = sb;
    }

    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        var gzipStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true);
        return sb.WriteToAsync(gzipStream).ContinueWith(_ =>
        {
            gzipStream?.Dispose();
        });
    }

    protected override bool TryComputeLength(out long length)
    {
        length = -1L;
        return false;
    }
}

SerializeToStreamAsyncメソッドでは、Utf8ValueStringBuilder のWriteToAsyncメソッドを使って生成したGZipSteamにコンテンツを書き込んでいます。また、完了したあとに、GZipStreamのDisposeが呼び出されるようにしています。ZStringではない別のもので受け渡したい場合は、コンストラクタで受け取ったものをGZipStreamに書き込むように処理を変えれば問題ないはずです。 また、コンストラクタではContent-TypeContent-Encodingヘッダーを設定しています。TryComputeLengthはコンテンツのサイズが低コストでわかる場合のみ正しい数値を返すので、今回は計算しないように実装しています。

docs.microsoft.com

利用するときはこのようになります。

// サンプルなので都度生成しているが、HttpClientのインスタンス生成は適切に。
var client = new HttpClient();
var endpoint = "https://example.com/log/v1";
using var sb = ZString.CreateUtf8StringBuilder();
sb.Append("[{\"logs\": [");
// さらにsbに追記
sb.Append("]}]");
var res = await client.PostAsync(Endpoint, new GZipZStringContent(sb));
res.EnsureSuccessStatusCode();

なお、処理全体としては最近公開したライブラリのこのクラスに記述しています。

github.com

NupkgファイルをGiHub PackageにPublishするGitHub Actionを作りました

github.com

というGitHub Actionを公開しました。

作った背景ですが、まずGitHub Actionsの中でGitHub PackageにNuGetパッケージを公開する手順は、Windows限定で昨日の記事に書きました。

tech.tanaka733.net

公開リポジトリならWindowsでも無料なので気にする必要もないのですが、プライベートリポジトリだとLinuxの2倍料金がかかるのでLinuxで対応したいこともあるでしょう。

help.github.com

Linuxの場合、nuget.configファイルで設定しないといけないのは昨日の記事の通りです。この中で、TOKENをyamlの中に直接書くわけにもいかなので、引数で渡した処理しないといけないというのも書いた通りです。bashでXML扱うのも面倒ですが、クロスプラットフォームだとPowerShell Coreもシェルとして利用できるので、これで対応することも可能です。

help.github.com

なのですが、何度も長いスクリプト埋め込むのもいやなので、練習がてらGitHub Actionとして作成することにしました。実装は、安直にnuget.configファイルをプロジェクトルートに配置してから、dotnet nuget pushコマンドを実行しています。

github.com

今、GitHub HackathonでGitHub Actionsを作って公開すると応募できるので、ぜひこのActionを使って、その次は作成してみてください。

githubhackathon.com