人間とウェブの未来

「ウェブの歴史は人類の歴史の繰り返し」という観点から色々勉強しています。

ngx_mruby v2のHTTPクライアントをv1よりも最大90倍高速にした

写真のような感じでRubyKaigi2018で登壇し、RubyKaigiを経て、ようやくngx_mrubyのv2をリリースしました。基本的にv1と互換性がありますので、今後はv2を開発していくことになります。

github.com

ngx_mruby v2の目玉機能としては、Rubyスクリプトからノンブロッキングのsleepとhttp[s]クライアントを使えるようになったことです。実装的には、nginxのsub requestという機能をうまく使って、ノンブロッキングのhttp[s]クライアントを汎用的なsub_requestメソッドとして実現しています。

では、本エントリではそのノンブロッキングhttpクライアントがどの程度高速処理可能になったかを実験してみましょう。また最後には、RubyKaigi2018の感想も述べます。

  • 実験
    • proxyサーバのblockingとnon-blockingのhttpクライアントの設定
    • apiサーバの設定
  • 実験結果
    • レスポンスタイムを変化させたベンチマーク
    • 同時接続数を変化させたベンチマーク
  • まとめとRubyKaigi2018の感想
続きを読む

第二回 #wsa研 でHTTPリクエスト単位でコンテナを再配置する仮想化基盤の高速なスケジューリング手法について発表しました

第二回Web System Architecture研究会で、タイトルの内容について発表してきましたので、そのスライドと予稿を以下に公開します。

speakerdeck.com

英文タイトルは以下の通り。

Fast Scheduler for a Cloud Platform to Relocate Containers at Each HTTP Request

クラウドサービスの普及に伴い,個人のWebサイトでもクラウドサービスに類する機能を利用して,突発的なアクセス集中に耐性があり,かつ,利用したリソース使用量に応じて課金するサービスの提供が求められている. 我々はその要求に応じるために,Webサイトをコンテナ上に構築し,コンテナの起動や停止,複製やリソース割り当てといった状態の変更を素早く実行できるコンテナ管理アーキテクチャFastContainerを提案した. 一方で,単一のコンテナが特定のサーバに収容されている状態で,サーバが過負荷に陥ったり停止したりするような状況においては,障害時にコンテナの収容サーバ情報の変更を手動で構成管理データベースに反映させる必要があった. 本研究では,HTTPリクエスト処理時において,収容サーバ,および,その経路までの状態に応じて,自動的にコンテナを収容するサーバを決定し,サービスを継続させる,HTTPリクエスト単位でのコンテナスケジューリング手法を提案する. 提案手法では,FastContainerの状態変化の高速性に基づいて,コンテナが頻繁に収容サーバを変更されてもサービスに影響がないことを利用する. それによって,プロキシサーバから収容サーバに1個のICMPパケットで応答速度を計測し,少ないパケット数と応答速度に基づいた短いタイムアウトで収容サーバの反応時間を計測できる. そのことで,単一のコンテナであっても,HTTPリクエスト単位でのコンテナスケジューリングを実現し,可用性を担保する.

  • 1. はじめに
  • 2. Webサービス基盤の負荷分散と可用性
    • Webホスティングサービス
    • クラウドサービス
    • FastContainerアーキテクチャ
  • 3. 提案手法
  • 4. 実験
    • 予備実験
      • ICMPタイムアウトに関する予備実験
      • Apacheコンテナの起動時間に関する予備実験
    • 再配置時のレスポンスタイムの本実験
  • 5. まとめ
    • 接続待ちソケット生成前のプロセスイメージを利用したWebサーバの汎用的な起動時間短縮手法
    • 参考文献
続きを読む

OSレイヤでWebサーバが起動時に実行するシステムコールを監視し起動完了直前のプロセスをイメージ化する

今回は、Webサーバの実装に依存することなく、OSレイヤでWebサーバソフトウェアが起動時に実行するであろうシステムコールを監視して、そのタイミングでプロセスをイメージ化する方法(PoC)について紹介します。

続きを読む

HTTPリクエスト単位でmrubyのバイトコードをProcとFiberで包みなおして実行した場合の性能とv2について

ngx_mrubyのv2の4月リリースに向けて、HTTPリクエスト単位で実行されるRubyのコードを、FiberとProcで包んだオブジェクト経由で実行する実行方式に実装しなおしています。これまでのngx_mrubyのv1系は、Rubyのコードをnginx起動時にstruct RPocにコンパイルしておき、リクエスト毎にそのバイトコードを実行していました。

一方v2では、nginx起動時にコンパイルされたstruct RProcを、HTTPリクエスト時にprocオブジェクトに変換した上で、そのprocオブジェクトをcallする処理をfiberで包み、そのfiberオブジェクトをresumeする処理をさらにprocで包んで、procをcallで実行するようにしました。それをC側とRubyのコードを行き来しながらうまいことnginxとmruby間のイベントループの上に乗るようにします。今のところはCとRubyの世界のコンテキストを現状のmrubyでうまく行き来するために、こういった複雑な方式にしています。その理由については一旦省略します。

github.com

続きを読む

2018年の抱負 - Webホスティングサービスの技術を体系化したこととその意図について

2018年の電子情報通信学会論文誌BのVolume J101-B No.1(発行日:2018/01/01)「ネットワーク社会に向けたインターネットアーキテクチャ論文特集」に、我々が執筆した「Webサーバの高集積マルチテナントアーキテクチャと運用技術」という招待論文が掲載されました。オープンアクセスで誰でもダウンロードして読むことができますので是非ご覧下さい。

続きを読む

ngx_mruby v1.20.0で動的listener設定をサポートしました

タイトルの通り、ngx_mrubyのhttpモジュールとstreamモジュール両方で、mrubyによる動的Listener設定をサポートしました。

動的Listenerとは、nginxのlistenの設定をmrubyで書いて、起動時に動的に設定を読み込めるようにできる機能です。以下の例を見た方が分かりやすいかと思います。

# $ ulimit -n 60000

worker_processes  1;

events {
    worker_connections  30000;
}

daemon off;
master_process off;
error_log logs/error.log debug;

stream {
  upstream dynamic_server {
    server 127.0.0.1:8080;
  }

  server {
      mruby_stream_server_context_code '
        (20001..30000).each { |port| Nginx::Stream.add_listener({address: port.to_s}) }
      ';

      mruby_stream_code '
        c = Nginx::Stream::Connection.new "dynamic_server"
        c.upstream_server = "127.0.0.1:#{Nginx::Stream::Connection.local_port * 2}"
      ';

      proxy_pass dynamic_server;
  }
}

http {
    server {
        mruby_server_context_handler_code '
          s = Nginx::Server.new
          (20001..30000).each { |port| s.add_listener({address: (port * 2).to_s}) }
        ';

        location /mruby {
          mruby_content_handler_code 'Nginx.rputs "#{Nginx::Connection.new.local_port} sann hello"';
        }
    }
}

このように書くと、nginxのTCPロードバランサが20001ポートから30000ポートまでListenした上で、接続のあったポート番号の数値の2倍のポート番号へTCPプロキシします。さらに、nginxのhttp側では20001ポートから30000ポートの2倍のポート番号でListenし、そのポート番号に応じてhelloを返します。

この設定でnginxを起動すると、これまでlistenディレクティブを沢山並べる必要があり、CRubyとerbなどを使ってプロビジョニング時にゴニョゴニョ書かなければいけなかった処理を、スッキリとmrubyで書くことができるようになります。また、ポート番号をデータベースやホストの状態に合わせていい感じにリッスンするような実装も可能でしょう。

$ netstat -lnpt | grep nginx | wc -l
20000

上記のような設定でcurlでアクセスすると、以下のようにレスポンスが返ってきます。

[ubuntu@ubuntu-xenial:~]$ curl http://127.0.0.1:20001/mruby
40002 sann hello
[ubuntu@ubuntu-xenial:~]$ curl http://127.0.0.1:20002/mruby
40004 sann hello
[ubuntu@ubuntu-xenial:~]$ curl http://127.0.0.1:29999/mruby
59998 sann hello

ふむふむなるほどべんり!

ということで皆様是非ngx_mruby v1.20.0の新機能をご活用ください。

github.com

nginxのworkerプロセス数をCPUコア数の倍数で自動的に設定できるモジュールを書いた

nginxはworkerプロセスの数をCPUコア(スレッド)数で決定するworker_processes autoという便利設定があります。

これが多用されているのは、nginxがノンブロッキングでリクエスト処理を行うため、コンテキストスイッチなどを考慮した場合に、コア数で立ち上げておけば効率よくCPUを使い切れるという前提があるからです。

一方で、例えば僕の用途では、現在画像の処理だったりとか、ngx_mrubyのようにリクエストの過程で一部ブロッキングされるような処理も増えてきているため、コア数以上の値に設定しておいた方が性能を発揮できるような状況も増えてきています。

そうなると、現状、非常に便利な設定であるauto設定を使えずに静的に数字を設定する必要があり、例えばサーバリプレース時には古いコア数を考慮した値になっていたりして、リプレース後にCPUのコア設定がそのままで性能が出せていないという事故が起きる場合もあります。また、だいたいCPUコア数の2倍ぐらいにしておけば、処理を効率的にさばけることがわかっていても、サーバのコア数に応じてworkerプロセスの値を静的に書き換えないと行けないこともあり面倒でした。

そこで、auto設定によってコア数を自動で取得した上で、そのコア数の何倍のworker数とするかをルールとして記述できるようにしておけば、サーバのコア数に依存しないworker数を定義できる上に、設定が動的になりメンテナンスしやすくなります。

ということで、論文の解放感から勢いで作りました。

github.com

設定はREADMEの通りで、例えばCPUコア数が4の場合は、autoで設定すると4個のworkerプロセスが起動します。そのような状況で、

worker_processes        auto;
worker_processes_factor 3;

のように設定すると、4コアの3倍の12個のworkerプロセスとして起動するようになります。

是非ご活用ください。

また、倍数だけでなく、autoで取得した値を色々カスタマイズできると良いなとは思っているので、何か案がありましたら是非PRを頂けるとマージします。