Elixirには、HTTPoisonとHTTPotionという名前の 非常に紛らわしい HTTP clientライブラリがあります。
この2つの違いについて、大きいデータをDLする時にどちらが良いだろう?という観点から、すこし調べた結果をまとめてみました。
調べてわかること
まず、調べてすぐに出てくるのが、elixirforumでの議論です。
ここから得られる情報で、だいたい以下のことが分かります
- Github starの数で見るかぎりHTTPoisonの方が知名度が高い
- どちらも裏側でErlangのHTTP clientを使っているが、HTTPoisonはhackneyを使い、HTTPotionはibrowseを使っている
- HTTPotionはHTTPS URLアクセス時にサーバ証明書の検証を行ってくれない
ここだけ見るとHTTPoisonの方が良さそうな気持ちになりますが、
冒頭で記載したとおり、今回は大きいデータをHTTP GETで取ってくる際の性能に興味があったので、その観点でもう少し調べます。
続いて、自分の興味に近い話をしているのが、ElixirでHTTP streamingする話 です。
ここからは、だいたい以下のことが分かります。
- 非同期DLは当然必須である
- HTTPoisonで使われるhackneyは、非同期DLのコードを書いても、時々全データをRAMに載せようとすることがあるらしい(!?)
- 非同期に加え、stream_next関数を呼ぶまで次のメッセージを受信しないonceオプションがあるが、Httpotionはこのオプションを直接指定できない。
- ibrowseオプションとして与えるのは可能だが、その際はibrowse型のメッセージを受信する必要がある
それだったらibrowse直接使うよね…
- master branchにonceオプションを直接呼べるようにしようとしている形跡ならある。
- ibrowseオプションとして与えるのは可能だが、その際はibrowse型のメッセージを受信する必要がある
自分の目で確かめてみたこと
百聞は一見に如かずということで、HTTPoisonまたはHTTPotionで指定URIをGETするescriptを書き、ファイルを非同期にDLするときの挙動を調べることにしました。
./dlixir -l httpoison|httpotion [--once] -u http://my-server/bbb1G.mp4
みたいな形で、使うライブラリとonceオプションの有無を指定できるようにしています。
実験概要
- escriptの事項は自宅メイン機のvirtual box上で動かしているUbutu 16.04 (特筆しない限りメモリ4GB, プロセッサ数 4)
- ダウンロード対象は自宅サーバ上の1GBくらいのmp4ファイル
- httpoison,httpotionのどちらを使うか / onceオプションの有無 の4パターンを実行し、以下を調べて見る
- timeコマンドにより実行時間を調べてみる
- topコマンドによりCPU・メモリ使用率を調べてみる
実行コマンドはおよそ以下の様になります。
top -d 1 -bc | grep beam > httpoison$i.txt &
(time ./dlixir -l httpoison -u http://my-server/bbb1G.mp4 -o test.mp4) >> log_httpoison.txt 2>&1
kill $(jobs -p)
実験結果と考察
timeコマンドによる実行時間
25回の試行のreal, user, sysの値の平均値を取った結果が以下です。
onceオプション無 | onceオプション有 | |
---|---|---|
httpoison | real: 44.66s user: 41.69s sys: 26.42s |
real: 53.17s user: 33.40s sys: 20.08s |
httpotion | real: 60.22s user: 37.84s sys: 9.08s |
real: 82.66s user:57.10s sys:28.38s |
実時間(real)の比較だけで単純に見ると、httpoisonの方がhttpotionよりも全般的に速いようです。
しかし、user実行CPU時間とsystem実行CPU時間の和(user+sys)で見ると、httpotion (onceオプション無) → httpoison(onceオプション有) → httpoison(onceオプション無し) → httpotion(onceオプション有) の順で速い、という不思議な結果が得られました。
user+sysがrealよりも大きくなる場合と言うのは、CPUコアが複数ある時に処理を分散してると起こり得る症状です。
プロセッサ数が1つだった場合は実行時間の逆転があり得そうです。
…が、追加実験をしたところ、目に見えて違いが見える程ではなさそうです
$ time ./dlixir -l httpotion -u http://my-server/bbb1G.mp4 -o test.mp4
finished!
real 1m7.841s
user 0m35.780s
sys 0m5.348s
$ time ./dlixir -l httpoison -u http://my-server/bbb1G.mp4 -o test.mp4
finished!
real 1m0.106s
user 0m28.716s
sys 0m9.208s
$ time ./dlixir -l httpotion -u http://my-server/bbb1G.mp4 -o test.mp4
finished!
real 1m5.822s
user 0m35.872s
sys 0m5.788s
$ time ./dlixir -l httpoison -u http://my-server/bbb1G.mp4 -o test.mp4
finished!
real 1m16.552s
user 0m36.992s
sys 0m9.060s
と言うか、プロセッサ数が4であった時に比べて、httpoison側のsysの時間が大分短くなっています。
コンテキストスイッチか何かで時間を使っているとかかもしれません。
topコマンドによる負荷状況
次に、各パターンで25回ずつ行った試行それぞれについて、topコマンドから得られるcpu使用率・メモリ使用率をマッピングしていきます
CPU負荷の比較(横軸は時間、縦軸は使用率)
onceオプション無 | onceオプション有 | |
---|---|---|
httpoison |
httpoison(onceオプション無)が他に比べてCPUを酷使してるようです。大体2コアくらい使って処理してるように見えます。
httpotion(onceオプション無)は、何故か安定してCPU使用率が100%あたりまでです。
onceオプションをつけるとhttpoisonもhttpotionも使用率が150%あたりでどちらも変わらなくなるようです。
メモリ使用率の比較(横軸は時間、縦軸は使用率)
onceオプション無 | onceオプション有 | |
---|---|---|
httpoison | ||
httpotion |
onceオプション無しだと書き込み処理のスピードに描かわらずデータをメモリにおいてしまおうとするためか、基本的にかなりメモリも食うようです。
特にhttpoisonのonceオプション無しはデータのかなりの量(4GBの20%なので800MBくらい)をデータにのせようとしていそうです。
逆にhttpotionはonceオプション無しでもメモリの使用量はそれなりの値(5%前後なので200MBくらい)に収まっていました。
こう見ると、onceオプション無しで良さそうに見えますが…
httpotionをonceオプションなし使っていて不穏な挙動が見えるので、個人的には非推奨です
$ time ./dlixir -l httpotion -u http://my-server/bbb1G.mp4 -o test.mp4
eheap_alloc: Cannot allocate 3280272216 bytes of memory (of type "heap").
httpoisonとhttpotionのメモリの図が逆…というわけでもないですし、
そもそも1GBのファイルDLで3GBくらいのメモリを割り当てようとしているようで、メモリリークしてそうでだいぶ不穏です。
現在の自分の実装がエラーケース等あまり見てない&close処理などなおざりにしてるせい、という可能性もあります。
まとめ
HTTPoisonとHTTPotionそれぞれを使って大きめのファイルをHTTP GETしてみた際の挙動を調べてみました
- HTTPoison onceオプションなしが一番速いが、リソース食うのが許される時に限る
- 許される時=メモリに一度DLしても良い時なので、非同期DLが求められることは少なさそうだが…
- 上のような例を除いては、HTTPoisonでもHTTPotionどちらでも基本的にはonceオプションはつけた方が良さそう
- でもHTTPotionは直接このオプション指定できないんだよなぁ…
- ElixirでHTTP streamingする話 曰く、onceをつけた上でもHTTPoisonはメモリ上に全データを載せることがあるらしいが…今回の実験の範囲ではでてこなかった
- 素直に使う分には、HTTPoisonの方が性能は良さそうである
- デフォルトのオプションでは、という話なので、チューニングの余地はありそう
- ibrowseはチューニングすると速いらしい(今回の試験には効かなさそうだが)
おまけ
蛇足ですが、実装したり色々調べたりしてるうちに見つけた話をいくつかおまけとして書いておきます。
- onceオプションをつけた時のstream_next関数の呼び方はHTTPotion(と言うかibrowse)とHTTPoisonで微妙に違う
- ibrowseにonceオプションをつけた場合は初回からstream_next関数を呼ぶ必要がある。
- HTTPoisonでonceオプションをつけると初回メッセージはstream_next関数は不要
- そもそも初回メッセージが違う。ibrowseはヘッダ(Status Code付き)を受信し、httpoisonはStatus(ステータスコードとか)を受信する
- HTTPotionのメッセージはデフォルトだと1MBくらいの単位で送られるが、HTTPoisonは1KBくらいの単位
- HTTPotionを使う時はtimeout時間がデフォルト5秒で設定されているので、今回のようなユースケースではオプションに
timeout: :infinity
を適宜入れる必要があった