人間とウェブの未来

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

mod_mrubyとngx_mrubyの次の一手となる新しいソフトウェア

スクリーンショット 2014-12-25 01.22.15.png

この記事は、mod_mruby ngx_mruby advent calendar 2014の最終日25日の記事になります。

24日は @cubicdaiya さんの「ngx_mrubyとngx_lua」でした。

無事カレンダーの全ての日程が埋まるような結果となり、非常にうれしいです。また、自分の作ったソフトウェアを使って色々工夫されているのをみて、非常にワクワクしながら全ての記事を読みました。とてもおもしろかったです。

というわけで今日は最後になるので、mod_mrubyとngx_mrubyの開発で感じていた事、そこからの次の一手として考えついた新しいソフトウェアを紹介したいと思います。

mod_mrubyとngx_mruby開発で感じていた事

これまでのHTTP/1系で実装されてきた代表的なWebサーバであるApacheやnginxはそれぞれ固有の静的な設定を記述する必要があり、Webサービスの高度化に設定の自由度が追いつかなくなってきていると感じていました。

そこで、mrubyの生産性と性能に着目し、mod_mrubyとngx_mrubyという、RubyでWebサーバの振る舞いを制御できるモジュール型ソフトウェアを開発しました。mrubyのアプリ組込みでプログラマブルに設定が書ける事による生産性の向上と性能の両立が高いレベルで実現しつつあると思っています。

これからは実際に運用してみて、改善のサイクルを回し、必要な機能があれば追加していくという方向性に進めていく予定です。

一方で、既存のWebサーバに後付で実装したために、性能面や自由度で妥協せざるを得ない箇所が複数ありました。

例えば、

  • Rubyで動的に設定する箇所が限定される
  • 生産性と性能の両立と最適化がミドルウェアのモジュール実装に依存する
  • 結局ミドルウェア自体の独自の設定記述から逃れられない

等が挙げられます。

mod_mrubyやngx_mrubyでやっていることというのは、10年も前からされていることで、既存手法の課題の明確化やインタプリタ組込みの最適化という点では新規性があり、スクリプト言語によるWeb機能拡張の一つの答えにはなったと自負していますが、上記の点においてはやはり多くの妥協がありました。

mod_mrubyとngx_mrubyの次の一手を考える

mod_mrubyやngx_mrubyのコアが大体出来てきたら、上記の課題を元にどういう未来が考えられるか、次の一手をどう進めるのが良いか、という事をちょうど1年前のこの時期に考えていました。

1年前のこの時期は、子供が3月ぐらいに生まれる事がわかっており、29歳最後の月でもありました。20台に代表的なOSSを作るという目標においては、mod_mrubyやngx_mrubyを作る事ができたのですが、すぐ30歳台に突入してしまいます。

できれば、子供が生まれるまでに次の一手のソフトウェアのコア部分ぐらいまでは実装しておきたいと考えていました。

妥協するぐらいなら1から作ろう

そんな時期的な背景があった当時に、上記の問題を考慮し、「妥協するぐらいだったら、1からmrubyに最適化したWebサーバを作ろう」「どうせならHTTP/2で」と思いつくに至りました。

そして、そこから設計を色々と考えつつ子供が生まれる3月までに必死で実装し、子供が生まれる当日も部屋で実装しながら妻に陣痛がきていないか部屋を行ったり来たりしながら開発していたのを覚えています。

そうして生まれたのが、

Trusterd HTTP/2 Web Server

です。

一からHTTP/2 Webサーバを実装するにあたって、コアの設計段階からRubyで全ての設定がかけること、及び、それによって性能を劣化させないようにする事を重視しながら設計・実装しました。

また、mod_mrubyやngx_mrubyを実装することで、Apacheやnginxの内部実装やデータ構造をある程度知っていたので、それらを取り込みつつ、自分のApacheモジュールやnginxモジュールの経験をふんだんに詰め込んで実装しました。

そういう意味では、mod_mrubyとngx_mrubyの開発で得たWebサーバとmrubyのアプリ組込みに関する知見は、Trusterdの実装において大きく影響しており、昔の苦労が今の実装に活きていると感じました。

その結果、 mrubyに真に最適化され、高速に動作しながらも設定の自由度が高く、機能拡張もRubyで書くことが可能な次世代HTTP/2 Webサーバが生まれたのでした。

mod_mrubyとngx_mrubyの次の一手はTrusterd

Trusterdによって、mod_mrubyやngx_mrubyで妥協していた問題が全てクリア可能になりました。

高速に動作する

設定はもちろんRubyで全て書けますし、機能拡張もRubyで書けます。また、本体自体はC言語で実装しており、スクリプト言語で振る舞いを定義できるWebサーバとしては非常に高速に動作します。1から実装している強みもあり、今後も生産性と性能のバランスが取りやすく、自分の実力の範囲内で最適化することができます。妥協する必要はもはやなくなりました。

h2oと呼ばれる非常に高速に動作するHTTP/2 Webサーバを @kazuho さんが作っていらっしゃいますが、そこのREADMEでもベンチマークでとりあえげて頂き、Rubyで設定が書けたり機能拡張できたりする割りにはかなりの性能が出ている事が分かりました。


ref: https://github.com/h2o/h2o#benchmarks

HTTP/2

Server \ size of content 6 bytes 4,096 bytes
tiny-nghttpd (nghttpd @ 9c0760e) 146,506 77,352
trusterd @ 962d031 125,482 50,103
H2O @ 7505a82 201,077 90,810

note: h2load -c 500 -m 100 -n 2000000 on Ubuntu 14.04 on VMWare Fusion


昨晩、久々に時間があったのでTrusterdの効率の悪い所を直したので、今のTrusterdだとこのベンチマークの値より2万req/secぐらいは向上するんじゃないかなと思っています。

また、 以下のようにも評価を頂き、真面目に実装したかいがあったなと思いました。

全ての設定や機能拡張をRubyでかける

続いて、Trusterdでは設定や機能拡張を全てRubyで書けるようにしています。

例えば、設定のサンプルファイルを以下にざっと載せます。まるでRubyのサーバみたいですが、内部実装は全てC言語で実装しており、設定、というかWebサーバの振る舞いの定義をmrubyで切り出しているというイメージです。

SERVER_NAME = "Trusterd"
SERVER_VERSION = "0.0.1"
SERVER_DESCRIPTION = "#{SERVER_NAME}/#{SERVER_VERSION}"

root_dir = "/usr/local/trusterd"

s = HTTP2::Server.new({

  #
  # required config
  #

  :port           => 8080,
  :document_root  => "#{root_dir}/htdocs",
  :server_name    => SERVER_DESCRIPTION,

  # required when tls option is true.
  # tls option is true by default.
  #:key            => "#{root_dir}/ssl/server.key",
  #:crt            => "#{root_dir}/ssl/server.crt",

  # listen ip address
  # default value is 0.0.0.0
  # :server_host  => "127.0.0.1",

  #
  # optional config
  #

  # debug default: false
  # :debug  =>  true,

  # tls default: true
  :tls => false,

  # damone default: false
  # :daemon => true,

  # callback default: false
  # :callback => true,

  # connection_record defualt: true
  # :connection_record => false,

})

#
# when :callback option is true,
#
# s.set_map_to_strage_cb {
#
#   p "callback bloack at set_map_to_strage_cb"
#   p s.request.uri
#   p s.request.filename
#
#   # location setting
#   if s.request.uri == "/index.html"
#     s.request.filename = "#{root_dir}/htdocs/hoge"
#   end
#   p s.request.filename
#
#   # you can use regexp if you link regexp mrbgem.
#   # Or, you can use KVS like mruby-redis or mruby-
#   # vedis and so on.
#
#   # Experiment: reverse proxy config
#   # reciev front end with HTTP/2 and proxy upstream server with HTTP/1
#   # TODO: reciev/send headers transparently and support HTTP/2 at upstream
#
#   if s.request.uri =~ /^\/upstream(\/.*)/
#     s.upstream_uri = $1
#     s.upstream = “http://127.0.0.1“
#   end
#
#   # dynamic content with mruby
#   if s.request.filename =~ /^.*\.rb$/
#     s.enable_mruby
#   end
#
#   # dynamic content with mruby sharing mrb_state
#   if s.request.filename =~ /^.*\_shared.rb$/
#     s.enable_shared_mruby
#   end
#
#
# }

# s.set_content_cb {
#   s.rputs "hello trusterd world from cb"
#   s.echo "+ hello trusterd world from cb with \n"
# }

#
# f = File.open "#{root_dir}/logs/access.log", "a"
#
# s.set_logging_cb {
#
#   p "callback block after send response"
#   f.write "#{s.conn.client_ip} #{Time.now} - #{s.r.uri} - #{s.r.filename}\n"
#
# }

s.run

ここでは大体設定の雰囲気だけを掴んで頂ければと思います。TLS有無にも対応しています。

基本的には、リクエスト処理の各フェーズで幾つかRubyブロックで記述できるコールバックを用意しており、ある種の機能拡張のフックになっており、そこにRubyのコードを書くことで機能拡張ができます。

例えば、index.htmlからhoge.htmlの内部のリダイレクトは、s.set_map_to_strage_cbというコールバックのブロック内に、

if s.request.uri == "/index.html"
  s.request.filename = "#{root_dir}/htdocs/hoge.html"
end

と書いてやればよかったりします。

動的コンテンツにRubyスクリプトを使える

また、Trusterdは静的コンテンツだけでなく動的コンテンツにも対応しており、s.set_map_to_strage_cbコールバック内に、

# dynamic content with mruby
if s.request.filename =~ /^.*\.rb$/
  s.enable_mruby
end

# dynamic content with mruby sharing mrb_state
if s.request.filename =~ /^.*\_shared.rb$/
  s.enable_shared_mruby
end

などとかくと、mrubyインタプリタによって解釈されて動的にレスポンスを生成できます。

例えば、hello.rbという以下のファイルへアクセスがあると、

rputs JSON::stringify({:now => Time.now.to_s, :message => "hello world"}) 

ちゃんとこれをmrubyで実行してレスポンスを生成します。

$ nghttp http://127.0.0.1:8081/hello.rb
{"now":"Wed Dec 24 07:05:11 2014","message":"hello world"}

HTTP/2で動的コンテンツを使う必要あるのか?という問いですが、個人的には静的コンテンツだけでなく、ちょっとした動的レスポンス、例えばJSONデータを組み立ててレスポンスで返すような場合に、mruby程度の機能と軽量さは非常に適していると考えています。

現状でもmrubyのmgemで、JSONや外部データストアを幾つか利用できるので、今後ユーザの触るリッチな部分はデバイスのアプリで実装し、データはWebを介してAPIで取得するようなHTTP/2の時代になってくると予想した場合、この機能は割りと効いてくるんじゃないかと想像しています。

また、HTTP/2はこれまでのHTTPの使い方だけでなく、個人的には様々なミドルウェアが通信するプロトコルとしても有用だと考えていて、mruby程度の機能で動的にレスポンスを生成できるのは、次のステップにも有効なのではないでしょうか。

となると、やっぱり デバイスや各種ミドルウェア・CアプリケーションでもHTTP/2でやりとりしたい ですよね?

Trusterdのコアはmrubyのライブラリになっている

話は当然そういう流れになっていき、HTTP/2のライブラリやmrubyが動く程度のデバイスや、Cアプリケーション・ミドルウェア上でHTTP/2を実装し、通信したくなるはずです。

そういう要望にも答えられるように、設計の段階から、Trusterdのコアはmruby-http2と呼ばれるmrubyのHTTP/2モジュールとして実装し、mrubyのHTTP/2サーバ・クライアントライブラリとして完全に分離する事が可能です。

つまり、HTTP/2のライブラリやmrubyが動く程度のデバイスやアプリケーションにmrubyを組み込めば、自動的にmruby-http2によってHTTP/2でお喋りできるようになるのです。そのために、mruby-http2ではクライアントとサーバ両方の機能を実装しています。

そういう意味で、Trusterdの実装はHTTP/2のWebサーバ方面だけでなく、mrubyとmruby-http2を組み込む事でデバイスやミドルウェア間での通信にHTTP/2が容易に利用できるという貢献も含まれているのです。

まとめ

ということで、アドベントカレンダー最後はmod_mrubyとngx_mrubyの今後、そこからの次の一手としてTrusterd HTTP/2 Webサーバの紹介をしました。

Web系の技術者にとって、次世代のハイパフォーマンスHTTP/2 Webサーバの設定をRubyで設定できたり機能拡張できたりすることは非常に生産性が高く、恩恵があると思います。また、mrubyのライブラリとして利用することで、ミドルウェアやCのアプリケーション開発者や組込み技術者も、mrubyがのる程度のデバイス上でHTTP/2 Webサーバやクライアントを容易に実装可能となります。

その結果、IoTの世界においてHTTP/2で効率良くデバイス間で通信する世界も近いのではないかとも思っています。

mod_mrubyとngx_mrubyの次の一手として、mrubyとHTTP/2を組み合わせたTrusterdを開発することで、専門の違う技術者が協力しながら新たな技術開発が可能となる世界もそこまで来ている気がします。

以上が、mod_mruby ngx_mruby advent calendar 2014の最終日25日の記事になります!

今年は自分のソフトウェアのアドベントカレンダーを全て埋めるのが目標だったので、それを達成できて非常に嬉しいです。参加して頂いた皆様はお疲れ様でした!

それでは来年もこれまでどおり継続的にmod_mrubyやngx_mrubyは開発し続けますし、その次の一手としてTrusterdとmruby-http2も開発していきますので、皆様もご興味ありましたら是非とも使って頂いて、さらに気が向きましたらPull Requestを頂けるとわたくし幸いでございます!

それでは皆さんメリークリスマス!そして良いお年を!