2014年5月26日月曜日

mod_ratelimit: クライアント向けの帯域制限

レート制御モジュールは出力フィルタで実装されている。
環境変数 rate-limitsで接続ごとの送信データレートを指定できる。
単位はKB/秒。

<Location /downloads>
  SetOutputFilter RATE_LIMIT
  SetEnv rate-limit 400
</Location>
参考: http://httpd.apache.org/docs/2.4/mod/mod_ratelimit.html

指定するレートの値が環境変数なので、条件によって変更が効くという自由度があるが、他方、処理内でsleepしてタイミングをとっているのが event MPMとの組み合わせではいけていない気がする。
mod_dialup()との比較はどうだろうか。

この処理ではレート制御は個々のレスポンスデータの送信に適用される。サーバが処理する送信全体の帯域制御などの用途ではない。少数の大きなファイルのダウンロードなどで通信帯域が枯渇するといった問題を緩和するために利用されるのだろうか?(最近のTCPのレート制御事情がどうなっているのかは知らないが、既に帯域が消費されている状態からでは、後続の接続はなかなかスピードアップは計れない気がする。どうだろう)。

出力フィルタ処理を見ていく。
出力フィルタの概要 を前提にする。

2014年5月19日月曜日

出力フィルタの概要

apache 2.4で追加されている ratelimitモジュールは出力フィルタだ。
これを調べてメモを作っていたが、出力フィルタについての説明がないことに気がついた。

私もぼんやりとは分かっているつもりだが、きちんと説明できる自信もないので、出力フィルタについて調べて書いてみる。
まず大きな枠組みの説明をして、必要に応じて、細かい説明を今後別記事で起こしていこうと思う(最終的には何かまとまった記事になればと思う)。

フィルタについてはApacheプロジェクトの以下のページに概要が書かれている

Filters
http://httpd.apache.org/docs/2.2/en/filter.html

開発者向けのドキュメントにも詳しい説明が見られる。

How filters work in Apache 2.0
http://httpd.apache.org/docs/2.4/en/developer/filters.html

Guide to writing output filters
http://httpd.apache.org/docs/2.4/en/developer/output-filters.html


フィルタには2種類ある。入力フィルタと出力フィルタだ。
入力フィルタは、外部からネットワークを通って受け取ったデータを、メインの処理(Content Generator)に渡す前に加工する。
出力フィルタは、メインの処理(Content Generator)が送信するデータを、実際にネットワークに送出する前に加工する。
これらのフィルタは、設定によって、自由に追加削除できるものもある。

まず、出力フィルタを説明する。
入力フィルタもそのうちにまとめる。

フィルタとともにApacheの重要なコンポーネントにハンドラがある。
これもそのうちまとめたい。

その上でモジュールの説明を書けば、よいのではないか。


bucketとbrigade


出力フィルタの説明を書こうとすると、bucket brigadeの説明が必要だ。
bucketは辞書を引くとバケツ、と出ている。チャーリーとチョコレート工場に登場する少年の名前でもある。
brigadeは軍隊がらみの用語らしい。旅団とある。師団(division)よりは小さい。と言われても分からない。
今見たWikipediaでは1500~6000名程度の兵員によって構成される部隊、となっている。

bucket型の要素をリング構造で束ねているのが bucket brigadeであるので、バケットの兵員によって構成される部隊、ということかもしれないが、用語の由来はよく分からない。

このbucketにはいろいろな種類がある。
bucketは大きくは、データタイプとメタデータタイプに分類される。
データタイプのbucketには、例えば、ファイルや、ヒープメモリや、mmapメモリや、ソケット等がある。
メタデータタイプのbucketは処理制御用で、データの終端になるEOSや、データの途中でそこまでのデータを強制的に送信させるFLUSHなどがある。
bucketタイプごとにbucketの生成、破棄、読込等の機能が提供されている。
このいろいろなbucketをリング状に繋ぎあわせて構成して、bucket brigadeになる。

そして、この bucket brigadeが出力データの実体だ。
例えば、ヒープメモリタイプのbucketが内部のヒープメモリにHTTPレスポンスヘッダを持ち、
続いて、ファイルタイプのbucketがあり、ファイル名などの情報を持つ。
さらに続いて、EOR(リクエスト終端)のメタデータタイプのbucketがある。
リクエスト処理の進行の中で、ひとつの bucket brigadeにこういった一連のbucketが含まれることになる。


出力フィルタの処理の大雑把な説明


フィルタは階層をなしている。
出力フィルタとは、このbucke brigadeを上流のフィルタから受け取って、これを加工し、下流のフィルタに引き渡すという処理を行っている。
引き渡しの処理を行うのが、ap_pass_brigade()関数だ。


出力フィルタ関数自体は、次のようなプロトタイプを持っている。

(httpd-2.4.9/include/util_filter.h)
134 typedef apr_status_t (*ap_out_filter_func)(ap_filter_t *f,
135                                            apr_bucket_brigade *b);

ap_out_filter_func関数は、受け取ったbucket brigade b を処理し、
処理結果から bucket brigade bbを作成すると、
以下を実行することで、加工した bb を下流の出力フィルタ(f->next)に引き渡す。

ap_pass_brigade(f->next, bb);

最上流のフィルタに最初にbucket brigadeを引き渡すのはContent Geneartorだ。
静的なローカルコンテンツの処理の場合、これは default_handler が行う。

4248 static int default_handler(request_rec *r)
4249 {
 :
4369         status = ap_pass_brigade(r->output_filters, bb);
 :

そして、最下流の出力フィルタはデータをクライアントに向けて送信する役目を担う。
通常は、COREフィルタ、ap_core_output_filter()になる。


出力フィルタ階層の作成


Apache httpdは多くのフィルタ関数を提供している。

RATE_LIMIT出力フィルタは、出力するデータの送信レートを制御する。
HTTP_HEADER出力フィルタは、request_rec構造体にある情報をもとに、送信用のHTTPレスポンスヘッダのbucket brigadeデータを生成する。
DUMPIO_OUT出力フィルタは、受け取ったbucket brigadeの内容を読み込んで、エラーログに出力する。

このほかにも出力フィルタは多数ある。

出力フィルタは ap_register_output_filter()関数でApache httpdプロセス内に登録され、利用可能になる。
この関数で、フィルタ名をキーにしたトライ木 registered_output_filters に出力フィルタ関数が登録される。

登録した出力フィルタ関数は、そのままでリクエスト処理に利用されるわけではない。
適用したいフィルタを実際に適用するには、さらに、ap_add_output_filter()関数やap_add_output_filter_handle()関数で、リクエストの出力フィルタの階層に登録しなくてはならない。

リクエスト処理において、これらの関数により、各フィルタ関数の情報(ap_filter_rec_t 構造体)が、それぞれのフィルタのタイプに従って、コネクション情報(conn_rec構造体)やリクエスト情報(request_rec構造体)に追加される。

Apacheのドキュメントにあるフィルタタイプの説明をかいつまんで訳すと次のような感じになる。

  • コネクションフィルタは、一つの接続の開始から終了までの間有効なもの。
  • プロトコルフィルタは一つのリクエストの間有効となる。
  • リソースフィルタは一つのリソースの処理中有効なものだ。
    これも通常なら一つのリクエスト処理と同じものだが、しかし、Apacheの処理は、一つのリクエスト中にも内部的なリダイレクトや、サブリクエストの処理において処理リソースが変化する。

コネクションフィルタは、コネクション情報(conn_rec構造体)の出力コネクションフィルタ情報(output_filters変数)リスト上に登録される。前述のCOREフィルタは、このコネクションフィルタに属する(厳密にはNETWORKフィルタとなる)。
プロトコルフィルタは、リクエスト情報(request_rec構造体)の 出力プロトコルフィルタ情報(proto_output_filters変数)リスト上に登録される。
最後に、リソースフィルタは、リクエスト情報(request_rec構造体)の 出力フィルタ情報(output_filters変数)リスト上に登録される。

request_rec情報 のoutput_filters情報(出力フィルタ)は、proto_output_filters情報(出力プロトコルフィルタ)に連結しており、さらに、conn_rec情報のoutput_filters情報(出力コネクションフィルタ)に連結して大きなリストを構成している。
個々の出力フィルタは、リスト内ではファイルタイプの小さい順に並ぶことになる。
これが、つまり、出力フィルタの階層だ。
前述した default_handlerが指定しているのが、r->output_filters。rが request_rec情報で、r->output_filtersが出力フィルタの階層の最上位を指すことになる。

4369         status = ap_pass_brigade(r->output_filters, bb);


設定による出力フィルタの追加


registered_output_filters TRIE木にフィルタを登録する処理は、各モジュールやcore機能で初期化処理中に行われる。

これら登録済みの出力フィルタを処理中のリクエスト、コネクションのフィルタリストに登録するのは、CORE出力フィルタのように必要に応じて自動的に登録されるものと、設定によって追加されるものがある。

設定によってフィルタを追加するために使用されるのは、たとえば、SetOutputFilterディレクティブである。
以下の設定例は、 /downloads へのリクエストに対して、 RATE_LIMIT出力フィルタを適用することを定義している。

<Location /downloads>
  SetOutputFilter RATE_LIMIT
  SetEnv rate-limit 400 
</Location>

また、AddOutputFilter ディレクティブでは、ファイルの拡張子ごとにフィルタを設定することができる。
以下の設定例は、拡張子が shtml のリソースを処理する場合に、INCLUDEフィルタを適用し、その後DEFLATEフィルタを適用することを定義している。

AddOutputFilter INCLUDES;DEFLATE shtml

RemoveOutputFilterディレクティブはmAddOutputFilterで追加されたフィルタを、削除する。

以下の設定は、上記のAddOutputFilterで .shtmlのファイルに追加された INCLUDEフィルタ、DEFLATTEフィルタを適用出力フィルタの階層から削除する。

RemoveOutputFilter shtml

Apacheのドキュメントには以下のような複雑な設定例がある。
# Effective filter "DEFLATE"
AddOutputFilter DEFLATE shtml
<Location /foo>
  # Effective filter "INCLUDES", replacing "DEFLATE"
  AddOutputFilter INCLUDES shtml
</Location>
<Location /bar>
  # Effective filter "INCLUDES;DEFLATE", replacing "DEFLATE"
  AddOutputFilter INCLUDES;DEFLATE shtml
</Location>
<Location /bar/baz>
  # Effective filter "BUFFER", replacing "INCLUDES;DEFLATE"
  AddOutputFilter BUFFER shtml
</Location>
<Location /bar/baz/buz>
  # No effective filter, replacing "BUFFER"
  RemoveOutputFilter shtml
</Location>
http://httpd.apache.org/docs/2.4/en/mod/mod_mime.html#addoutputfilter

この例では、結局、 /bar/bas/buzへのリクエストの場合、追加の出力フィルタは登録されないことになる。