NiquestsとRequestsの `pool_maxsize` の違い

前回の記事 ではNiquestsに最近修正された深刻なバグがあることを紹介しましたが、それで HTTP/2 や HTTP/3 の利用を不安に思い HTTP/1.1 を使うことにしていました。そして HTTP/1.1 の場合に Requests と pool_maxsize の振る舞いが大きく異なることに気がつきました。

ちなみに pool_maxsize について初めて触れたのは Requests の設定についての記事でした。

urllib3 では PoolManager の maxsize で同時接続数を制限できてデフォルトで1なのですが、 requests ではこれを10に置き換えてしまっており、これはほとんどのアプリケーションにとっては過剰でしょう。最大接続数を超えてもデフォルトの設定ではブロックせずに新規接続してくれるので、基本的には maxsize=1 を使い、それで足りないような場合にだけ増やすのがいいと思います。

-- requestsで長時間Sessionを使う場合はidle_timeoutに注意

Niquestsはデフォルトで HTTP/2 は HTTP/3 に対応しており、それらを使った場合は1本の接続で複数のリクエストを多重化して送信します。なので何も設定しなくても大量の接続ができることはありません。

問題は HTTP/1.1 のときで、Niquestsは「デフォルトの設定ではブロックせずに新規接続して」くれません。 HTTPAdapterに pool_block=False を指定しても変わりません。どうやら一つのコネクションプールクラスでHTTP/1.1の接続とHTTP/2,3の接続を混ぜて使えるようにする上で生まれた制約のようです。

結果として、Niquestsで pool_maxsize=1 を設定してしまうと、本当に同時にリクエストを送信したくなった場合に接続が空くのを待つようになってしまい、WebアプリケーションからWeb APIを呼んでいるケースではレスポンスタイムの低下につながります。

対策は pool_maxsize を大きい値に設定することです。デフォルト値が10なので、10スレッド以上で並行アクセスするのでなければデフォルト値で構いません。しかしそうすると、一時的に多めに並行アクセスが発生した後に落ち着いても、たくさん作った接続がずっと解放されないままになってしまいます。

さて、RequestsからNiquestsに移行した最大の理由は keepalive_delay でHTTP/1.1接続の寿命を設定できることでした。 そしてNiquestsはHTTP/2やHTTP/3の接続の管理のためにバックグラウンドスレッドでコネクションプールのメンテ(PING送信)をしています。 そのバックグラウンドスレッドで寿命が来たHTTP/1.1接続のcloseもやってほしいと頼んだらすぐに実装してもらえました。 urllib3-future 2.17.902で実装されたので、Niquestsを使っていてkeepalive接続の繋ぎっぱなしが気になる人はこのバージョン以降にアップデートしてください。

ちなみに、 pool_maxsize が小さいときに接続待ちをしているリクエストがいる状態で、先に接続を使っているリクエストが接続エラーに遭遇すると、待っていたまだ送信していないリクエストまで道連れにエラーになってしまうというバグも発見して報告しています。この問題の修正には少し時間がかかるようですが、 pool_maxsize を大きめにして接続待ちを無くせばこのバグも回避できています。

https://github.com/jawah/urllib3.future/issues/323

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。