採用サイトHRブログngx_mruby2016-12-02ngx_mrubyで動的コンテンツキャッシュを実現するngx_mrubyツイートはじめに冬が近づき、寒くなると僕を思い出しますね?どうも、ホスティング事業部の@pyama86です。これまで開発者(@matsumotory)が身近にいながら、なかなか触る機会がなかったngx_mrubyを触ってみました。題材として我々が提供しているサーバホスティングにおいて、HTTPリクエストに対して、いかに早くレスポンスを返せるかということは、サービスの指標として非常に大事な要素の一つです。今回はその点において劇的な効果が見込めるコンテンツキャッシュについて、動的にキャッシュの利用URLを選択できる仕組みを実装しました。この記事ではその実装及びベンチマークについて扱いたいと思います。亮介(@matsumotory)によって開発されているOSSです。nginxに組み込むことで、nginxの設定ファイルや、nginxの各イベントにフックしてmrubyを実行し、nginxの動作をプラガブルに扱うことができます。httpdで同様の動作を実現できるのも強みの一つです。ngx_mrubyを利用した動的コンテンツキャッシュの概要nginxではモジュールを追加することにより、機能を拡張できます。デフォルトでngx_http_proxy_moduleが組み込まれているので、設定を記述するだけでコンテンツキャッシュを利用できます。コンテンツキャッシュを利用すると上記の図のように、1回目のリクエストに対するオリジンサーバのレスポンスをnginxがキャッシュし、2回目のリクエストはオリジンサーバにリクエストすることなくnginxがキャッシュから直接応答することで、オリジンサーバの処理時間を削減して、高速にレスポンスを返すことができます。この仕組みをそのままサーバホスティングで利用しようとした場合、数百万件のVirtualHostと、それに紐づくファイルをnginxの設定ファイルに全て定義すると、設定ファイルが膨大なサイズとなり、さらにキャッシュの対象URLを変更するたびにnginxの設定ファイルの変更、再読込が必要になるなど、システムとして非常に扱いづらい状態になることが想定されます。いった問題に対してngx_mrubyを用いて、下記のようなアーキテクチャで解決できます。前項のアーキテクチャを利用してサービスとしてお客様に提供することを考慮すると、次の要件を満たす必要があります。お客様の指定した任意のURLをキャッシュできるお客様の指定した任意のURLのキャッシュを削除する事ができるコンテンツキャッシュは画像や、htmlファイルなどの静的なコンテンツをキャッシュすることが主な用途と想定されます。対して動的なコンテンツはアクセスごとに内容が変化することから、キャッシュを必要としないケースが多いと考えられ、そういった運用の幅を広げるために任意のURLを指定できる仕組みが必要です。サイトのトップ画面となるindex.htmlなど更新した内容を即時反映したいケースもあるので、キャッシュの削除も機能として必要であると考えました。加えて、画像やcss、jsファイルなどはワイルドカードのように指定できたほうが用途に即しているので正規表現で対応しています。いった前提を踏まえて、作成した設定ファイルを見てみましょう。}}いくつかのブロックに分けて、ポイントを解説していきたいと思います。Redis、MySQLのコネクションをUserdataに保持するmruby_init_code内で、Redis、MySQLへ接続を行い、mruby-userdataへ保存しておきます。user-dataに保存することで、nginx起動後、リクエストごとにRedis、MySQLに接続することなくコネクションを使いまわすことができるようになるので、接続処理の重複を省く事ができ、処理時間を短縮できます。mruby_init_codeディレクティブはnginx.confのhttp{}セクションに設定を書くことができ、nginx.conf読込時にRubyスクリプトを実行できる機能です。これは以前から実装済みだったのですが、masterプロセス初期化時に実行されるので、そのタイミングでの実行を代替することができます。$do_not_cache;nginxのキャッシュの利用を制御するproxy_cache_bypass、キャッシュの保存を制御するproxy_no_cacheを$do_not_cacheという変数で制御しています。$do_not_cacheの内容については、mruby_set_codeを利用すると、第2引数に記述したRubyコードの戻り値を、第1引数の変数にセットできるので、MySQLからHTTPヘッダのHostをキーにcacheテーブルを検索した結果とリクエストURIを正規表現を用いて比較し、合致した場合にコンテンツキャッシュを保存、レスポンスに利用するよう実装しています。MySQLに登録されるレコードは以下のようなレコードになるでしょう。.+(jpg|png|gif)$.+\.html$.+intdex.php$なお、MySQLにリクエストのたびにクエリを実行すると、応答待ちの時間分レスポンスが遅くなるので、Redisにキャッシュ可否を保存し、次回からはレスポンス時間を短縮できるようにしています。キャッシュの削除を可能にするnginxではbuild時にngx_cache_purgeモジュールを追加することで、proxy_cache_purgeディレクティブを利用してキャッシュの削除に関する定義を行えます。"$scheme://$host$1$is_args$args";}上記の定義を行い、localhostから以下のようにURLアクセスすることで、キャッシュを削除できます。keep-alive今回は検証の便宜上、localhostからのアクセスとしましたが、実際にサービスに実装する場合は、APIサーバ経由などでキャッシュを削除できるようにすると、悪意を持った削除を防ぎつつ、任意のキャッシュを削除できます。Benchを利用して取得しました。2015)上で起動したVirtualBoxのコア1、メモリ512MBのVMを使用しました。5343rpsngx_mrubyを利用した場合、利用しない場合と比較して、30%未満ほどの性能劣化が見られる反面、キャッシュの有無の比較においては、キャッシュヒット率100%の環境で、4004rpsから10042rpsと倍以上のパフォーマンスが得られました。プロキシサーバの性能劣化については、オリジンサーバが動的コンテンツを扱う場合、プロキシサーバの処理時間と比べて、動的コンテンツの処理時間が長時間となるケースが多いことから、全体の処理時間から見ると30%程度のプロキシサーバの性能劣化であれば誤差といえます。動的キャッシュの利用でオリジンサーバのリソースを節約できるメリットを踏まえると十分有益であると結論付けられます。まとめngx_mrubyを利用することによって、今回紹介したようにオリジンサーバの前段のプロキシサーバで、L7の処理が可能になり、これまでのアーキテクチャで実現できなかったことがシンプルにできるようになりました。動的な処理をプロキシサーバで扱えるようになるとアーキテクチャの選択の幅が広がり、エンジニアとして非常に好奇心を掻き立てられます。httpdの.htaccessの機能を利用して、複雑な定義を行っている場合なども、mrubyを利用してシンプルな定義に置き換えるなど手軽に始められると思います。ngx_mruby編を見ていただくと、ホスティングに限らず、様々な用途で活用できることがご確認できるかと思います。最後に、今後も利用実績を随時アウトプットしていくので、イベントなどぜひ足を運んでください。~破壊的イノベーションをおこす革新的技術~」を計画しております。エンジニア新卒採用に関する情報をまとめたページも公開中!ツイート書いた人pyamaイケメンマエストロことpyamaです。ホスティング事業部のあれをやっています。