nginx + lua + ObjectStorage ファイルアップロード/ダウンロードの高速化 2014.12.11 Code the Clouds Mix-up Vol.2 株式会社MNU 雪本修一
nginx + lua + ObjectStorage ファイルアップロード/ダウンロードの高速化
2014.12.11 Code the Clouds Mix-up Vol.2 株式会社MNU 雪本修一
雪本 修一Shuichi Yukimoto株式会社MNU 代表取締役社長 電気通信大学の認定ベンチャーとして起業。 現在も現役プログラマとしてコードを書いている。 好きな言語はJavaScript,Scheme,Lisp,Python SoftLayerを使い始めて一年ぐらい 弊社はSoftLyerのリフェラルパートナーです。
twitter:@nsas454 facebook:shuichi.yukimoto
今日のお話
• PBOXで実装した技術についてのご紹介
• nginxの拡張モジュールを使ったアップロードとダウンロードの高速化を実現した仕組み
• nginxの拡張モジュールの実装方法について
nginx• ロシアのIgor Sysoev氏によって、1日に5億リクエストを処理するWebサイトのHTTPサーバーとして開発された
• 注目されるようになったのは、C10K問題(クライアント1万台問題、注2)が叫ばれるようになった2000年代後半です
Lua• 動的型付言語
• 高速に動作する
• ゲームなどで広く利用されている言語
• インクリメンタルGCで有名
• nginxもluaで実装されているそうです。
• wikipediaより
• pboxはnginx + gunicornで構成されているが、ファイルのアップロード、ダウンロード処理をgunicornでやるのは高コスト
• アップロード/ダウンロードには複雑な処理は必要無いにもかかわらず、gunicornのセッションを専有するのは良くない!
ファイル
nginx gunicorn object Storage
フロントエンド バックエンド ストレージ
認証/リクエスト
PUT
• nginx拡張でなるべく多くの同時リクエストを捌きつつ、静的ファイルの送受信を行う
ファイル
nginx object Storagegunicorn
redis tornade
LUA
フロントエンド バックエンド ストレージ
PUT
PUTリクエスト認証/ストレージ情報
何故nginx拡張を使うのか?• pboxはnginx + gunicornで構成されているが、ファイルのアップロード、ダウンロード処理をgunicornでやるのは高コスト
• アップロード/ダウンロードには複雑な処理は必要無いにもかかわらず、gunicornのセッションを専有するのは良くない方法
• nginx拡張でなるべく多くの同時リクエストを捌きつつ、静的ファイルの送受信を行う
nginx + HttpLuaModule
インストール
• luajitのインストール
• nginx + HttpLuaModuleのビルド&インストール
• 詳細は
• http://wiki.nginx.org/HttpLuaModule
nginx.conf• luaモジュールをインストールしたら、専用のディレクティブが使える
• 例えば、content_by_lua_file でcontentフェーズにluaを呼び出すことができる。
• 参考: http://wiki.nginx.org/HttpLuaModule#Directives
• nginxのフェーズについては http://wiki.nginx.org/Phases を参照
• レスポンス全体をluaで作る場合はcontentフェーズを使う。認証だけluaを使う場合はaccess_by_lua_file ディレクティブを使用するなどの使い分けを行う。pboxでどうしているかは後述
nginxのフェーズ
• nginx の Phase のうち、Rewrite, Access, Content, Log のフェーズに対してフックする仕組みを提供
lua-nginx-module が提供する主なフックの仕組み
• Rewrite Phase
• set_by_lua:変数の設定、ヘッダの操作、リダイレクト等が可能
• rewrite_by_lua:Rewrite Phase の最後で実施され、自由度の高い rewrite処理を実現可能
• Access Phase
• access_by_lua:自由度の高い認可処理を実現可能
• Content Phase
• content_by_lua:コンテンツの生成
• header_filter_by_lua:コンテンツ生成後、header に対するフィルタ処理(書き換えや追加など)に対応
• body_filter_by_lua:コンテンツ生成後、body に対するフィルタ処理
• Log Phase
• log_by_lua:ログ処理のタイミングで動作。ここでリクエストの情報を変数にためておくことで、nginx + lua だけで集計の仕組みを作ることも可能
luaで何ができるのか?
• 特に重要なのは以下の2つ
• サブリクエスト
• tcp/udpソケット通信
• 詳しくは下記で説明
サブリクエスト
• ざっくり言えば、他のlocationを呼ぶ機能
• ngx.location.captureを使用する
• 一度のリクエストで何回もサブリクエストを呼ぶことができたりする
tcp/udpソケット通信• ngx.socket.tcp / ngx.socket.udp でソケット通信ができる。luaの組込ソケットと互換のインターフェイスを持っているが、内部動作が違うのでかならずこちらを使うこと。
• このソケットはnon-blockで動作し、待ち時間で別のリクエストを処理してくれる
• pboxではこのソケットの上にHTTPプロトコルを実装したlua-resty-httpを使用してObjectStorageとの通信を行っている。
アップロード/ダウンロードの概要
• pboxのファイルアップロード/ダウンロードは以下の動作が必要
• 認証:リクエストヘッダで送られてくる認証トークンが正しいかSQLに保存された情報との比較を行う。また、ここでObjectStorageへのurl、認証情報を取得する
• ObjectStorageへのアップロード/ダウンロード:1で得られた情報を元に実際にファイルをアップロード/ダウンロードする
• (アップロードの場合のみ)SQLへアップロードしたファイルのメタ情報を保存する:ObjectStorageで管理できない追加情報をpbox持たせるため、ファイル・ディレクトリの情報はSQL側で持っている
HttpLuaModule• pboxはcontentフェーズでluaを使用し、
• 1. 認証を内部サブリクエスト
• 2. オブジェクトストレージへのアクセスにソケット
• 上記を使用しているが、次のようにする方法もあった(RestfulなAPIのために断念した)。
余談• ダウンロード: accessフェーズのみluaで行い、contentはObjectStorageへプロキシすることもできる(proxy_passディレクティブを使う)。
• アップロード: ObjectStorageへのアップロード前と後にluaの処理が入る。(またレスポンスはObjectStorageのものを直接使わず、メタ情報をjson返したい。)そのため、contentフェーズをluaで書く。
まとめ• nginxのフェーズの概念は超重要。1つは省力化のため。アップロードはproxy_passを使うことでluaの記述を減らすこともできる(pboxではやらないが)。
• もう1つは出来ることが違う。たとえば、accessフェーズでレスポンスの中身を書き換えることはできない。logフェーズや、post_actionではluaからサブリクエストが使えないなど、フェーズを意識する必要がある。
さいごに
• とりあえず、重要な事は2つだけ
• サブリクエスト(ngx.location.capture)はマジ便利
• 100% non-blockingなHTTP通信でObjectStorageへアクセスしてます
Appendix
location /internals { internal; proxy_pass http://backend; } location ~ /([^/]+)/files/(.*)$ { content_by_lua_file content_file.lua; }
nginx.conf
local http = require "resty.http"local cjson = require "cjson"
local user = ngx.var[1] -- location の $1 相当local path = ngx.var[2] -- location の $2 相当
--- 認証 local res = ngx.location.capture('/internals/auth', { args = { token = token, user=user, path=path },})
if res.status ~= 200 then ngx.exit(res.status)end
--- ObjectStorageへのURL,Tokenを認証時に返してもらう local body = cjson.decode(res.body)local host = body.hostlocal path = body.pathlocal token = body.token
--- 通信確立 local httpc = http.new()httpc:connect(host, 80)
--- ObjectStorageへGETリクエスト発行 local res, err = httpc:request{ path = path, method = 'GET', headers = {['X-Auth-Token'] = token}}
--- レスポンスボディをそのままnginxのレスポンスとして返す local reader = res.body_readerrepeat local chunk, err = reader(chunk_size) ngx.say(chunk)until not chunk
content_file.lua