アーキテクトの目から見た最近の.NET Core 2とC#

最近になって言語やフレームワーク選定、マイクロサービスでの活用、Dockerなどのコンテナでの活用、大量のREST APIアクセスなどの観点で、お客様などから相談を受ける機会が増えたので自分の為にも整理してみる事にした。

## OSS ##

未だにプロプライエタリソフトウェアと思われる事も残念ながら多いのだが、.net coreは、MITライセンス、Apacheライセンスで公開されているOSS、Microsoftだけでなく、RedhatやJetBrainsも開発に協力している。
気に入らないことがあれば修正する事もできるし、貢献する事もできる。

[https://github.com/dotnet/core/blob/master/roadmap.md](https://github.com/dotnet/core/blob/master/roadmap.md)

## 言語 ##

一度チームの仲間が言語を習得した後、今後に生かせるかどうか、プロジェクト離脱や転職後でもその言語を生かせるかなど様々な事を考えている。
開発後のソフトウェア資産を維持する事から考えれば、ECMA Internationalや国際標準化機構 (ISO) などの標準化も大事な要素になる。
サーバサイド、Unity、Unreal、Xamarinによるモバイルアプリ、Windows向けデスクトップアプリ(UWP)、Consoleアプリなど多少の素性の違いはあるが、C#ではあるので、様々なシーンでも利用ができる。

## 開発 ##

快適な開発環境は、開発者の生産性向上の為に不可欠だし、そもそも開発環境はこうじゃなきゃダメってルールを作るのは面倒な事が多い。
Windowsだけで開発ができると思いこまれている節があるが、SDK一つでMacやLinuxでも開発ができる。

[https://www.microsoft.com/net/core#windowscmd](https://www.microsoft.com/net/core#windowscmd)

重厚長大なVisual StudioのようなIDEを使うこともできるし、VS Codeのような軽量IDEでも良い、JetBrainsのツールも使える、EmacsやViなどでも良い。Visual Studio for Macなんてのもある。
好みに合わせて、好きなエディタを用いる事ができる。

## アプリケーションポータビリティ、クロスプラットフォーム ##

折角プログラムを書いたのに、プラットフォーム毎に書き直していたのでは、面倒極まりない事になる。
どこでも動くのは大事な事だ、プログラムをコンパイルしたら、以下のように、各プラットフォーム向け実行ファイルを吐き出せばいい。

	$ dotnet publish -c Release -r win10-x64
	$ dotnet publish -c Release -r linux-x64
	$ dotnet publish -c Release -r osx-x64
	$ dotnet publish -c Release -r linux-arm

あとは、各プラットフォーム向けに生成されたフォルダをコピーして、実行権限を付与して、実行すれば動く。
継続的インテグレーション(CI)/継続的デリバリー(CD)/継続的モニタリング(CM)などのプロセスを実現する上でテスト環境などを準備する事になるが、デリバリが楽になるのは良い。

## コンテナ ##

最近はコンテナを積極的に活用と仕様という話題を耳にすることも増えた、当然ながら、コンテナへのデプロイも楽である。
以下のようなDockerファイルを用意して。

	FROM microsoft/dotnet:2.0-sdk AS build-env
	WORKDIR /app
	
	# copy csproj and restore as distinct layers
	COPY *.csproj ./
	RUN dotnet restore
	
	# copy everything else and build
	COPY . ./
	RUN dotnet publish -c Release -o out
	
	# build runtime image
	FROM microsoft/dotnet:2.0-runtime 
	WORKDIR /app
	COPY --from=build-env /app/out ./
	ENTRYPOINT ["dotnet", "dotnetapp.dll"]

あとは、イメージを作るだけでいい。

	$ docker build -t dotnetapp-prod .
	$ docker run --rm dotnetapp-prod
	Hello .NET Core from Docker

## Webサーバ ##

何かと重いといわれ続ける事が多かったASP.NETだが、フロントのウェブサーバであるIISがモジュラー型でありフル機能のWebサーバであったからでもある。
今はNode.jsなどと同じように、軽量TCPサーバライブラリ(libuvやRIOなど)を用いた、ノンブロッキングIO実装WebサーバKestrelを持っている。

Kestrelは高速な反面、高機能Webサーバのような仕組みは無い、もちろんMicroservices向けのREST APIなど、そのままで動かしても良いし、Nginxのバックエンドとして稼働させたりするのが良い、IISでももちろん動く。

以下には、TechEmpowerテストなどを用いた、IIS/NodeJS/Scala/Nettyなどとの比較ベンチマークのソースコードがある。
環境や実装によって当然スループットは変わるのだが、多少なりとも参考になるだろう。
短絡的にRPSだけのリクエスト処理数勝負的な話では無いとしても、Scala、Netty、NodeJSと同じような立ち位置でKestrelが使えそうな感じである事くらいは参考になる。

[https://github.com/aspnet/benchmarks](https://github.com/aspnet/benchmarks)

## 実験のベースライン

これらは、さまざまなテクノロジスタックとアプローチのHTTP以外のオーバーロードを測定するためのサーバー実験です。
これらは一般的に、実際のHTTPサーバではなく、固定HTTPレスポンスを持つHTTPの要求に特別に応答するTCPサーバです。

| Stack | Server |  Req/sec | Load Params | Impl | Observations |
| ----- | ------ | -------- | ----------- | ---- | ------------ |
| Hammer (raw HTTP.SYS) | perfsvr | ~280,000 | 32 threads, 512 connections | C++ directly on HTTP.SYS | CPU is 100% |
| Hammer (raw HTTP.SYS) | perfsvr | ~460,000 | 32 threads, 256 connections, pipelining 16 deep | C++ directly on HTTP.SYS | CPU is 100% |
| libuv C# | perfsvr | 300,507 | 12 threads, 1024 connections | Simple TCP server, load spread across 12 ports (port/thread/CPU) | CPU is 54%, mostly in kernel mode |
| libuv C# | perfsvr | 2,379,267 | 36 threads, 288 connections, pipelining 16 deep | Simple TCP server, load spread across 12 ports (port/thread/CPU) | CPU is 100%, mostly in user mode |
| RIO C# | perfsvr | ~5,905,000 | 32 threads, 512 connections, pipelining 16 deep | Simple TCP server using Windows Registered IO (RIO) via P/Invoke from C# | CPU is 100%, 95% in user mode |

## Plain Text

TechEmpowerテストのプレーンテキストベンチマークと同様です。
サーバーとスタックのHTTP効率を強調することを目的としています。
実装は、レスポンス本体を積極的にキャッシュし、パフォーマンスを最大化するために必要でないコンポーネントを削除/無効化することは自由です。

| Stack | Server |  Req/sec | Load Params | Impl | Observations |
| ----- | ------ | -------- | ----------- | ---- | ------------ |
| ASP.NET 4.6 | perfsvr | 57,843 | 32 threads, 256 connections | Generic reusable handler, unused IIS modules removed | CPU is 100%, almost exclusively in user mode |
| IIS Static File (kernel cached) | perfsvr | 276,727 | 32 threads, 512 connections | hello.html containing "HelloWorld" | CPU is 36%, almost exclusively in kernel mode |
| IIS Static File (non-kernel cached) | perfsvr |231,609 | 32 threads, 512 connections | hello.html containing "HelloWorld" | CPU is 100%, almost exclusively in user mode |
| NodeJS | perfsvr | 106,479 | 32 threads, 256 connections | The actual TechEmpower NodeJS app | CPU is 100%, almost exclusively in user mode |
| NodeJS | perfsvr2 (Linux) | 127,017 | 32 threads, 512 connections | The actual TechEmpower NodeJS app | CPU is 100%, almost exclusively in user mode |
| ASP.NET Core on Kestrel | perfsvr | 313,001 | 32 threads, 256 connections | Middleware class, multi IO thread | CPU is 100% |
| Scala - Plain | perfsvr | 176,509 | 32 threads, 1024 connections | The actual TechEmpower Scala Plain plaintext app | CPU is 68%, mostly in kernel mode |
| Netty | perfsvr | 447,993 | 32 threads, 256 connections | The actual TechEmpower Netty app | CPU is 100% |

## Plain Text with HTTP Pipelining

Like the Plain Text scenario above but with HTTP pipelining enabled at a depth of 16. Only stacks/servers that show an improvement with pipelining are included.
上記のプレーンテキストのシナリオと同様ですが、HTTPパイプラインが16段で有効になっています。
パイプライン処理による改善を示すスタック/サーバーのみが含まれています。

| Stack | Server |  Req/sec | Load Params | Impl | Observations |
| ----- | ------ | -------- | ----------- | ---- | ------------ |
| NodeJS | perfsvr | 147,554 | 32 threads, 256 connections | The actual TechEmpower NodeJS app | CPU is 100%, almost exclusively in user mode |
| NodeJS | perfsvr2 (Linux) | 173,641 | 32 threads, 512 connections | The actual TechEmpower NodeJS app | CPU is 100% |
| ASP.NET Core on Kestrel | perfsvr | 1,174,881 | 32 threads, 256 connections | Middleware class, multi IO thread | CPU is 100% |
| ASP.NET Core on Kestrel | perfsvr2 (Linux) | 928,023 | 32 threads, 256 connections | Middleware class, single IO thread | |
| Scala | perfsvr | 1,514,942 | 32 threads, 1024 connections | The actual TechEmpower Scala plaintext app | CPU is 100%, 70% in user mode |
| Netty | perfsvr | 2,808,515 | 32 threads, 256 connections | The actual TechEmpower Netty app | CPU is 100% |

## まとめ ##

以前に比べて、C#という言語の活用範囲を考えれば、最近は案外悪くないものだなと思うようになった。
OSSとなってから、まだ数年であるが、十分な実用域に入ってきているのだと感じる。