プロキシプールには、容量が大きく異なるエンドポイントが含まれることがあります。アプリケーション全体で 50 件の操作を許可しても、そのうち 49 件が同じプロキシを選ぶのを防げません。

ポリシーが「選択した各プロキシを通る進行中の操作は最大 4 件」であるなら、そのポリシーをそのまま表現します。プロキシの識別子ごとにセマフォを保持し、プロキシの容量を消費する作業の周りでそれを取得します。

limits = {proxy: asyncio.Semaphore(4) for proxy in proxy_urls}

async def fetch(session, proxy, url):
    async with limits[proxy]:
        async with session.get(url, proxy=proxy) as response:
            return await response.read()

このスケッチは、asyncio がインポートされ、プロキシ URL が検証済みであることを前提としています。許可されたパブリックプロキシ経路での公開リクエストには、HTTP プロキシリストが aiohttp のプロキシ URL の候補を提供します。実際のアプリケーションでは、独自のリクエストタイムアウトを設定し、必要なレスポンスデータの量を決める必要もあります。大きなファイルを丸ごとメモリに読み込むことは、別のリソース判断です。

高コストな部分のために許可を保持する

レスポンスヘッダーが届いた時点でセマフォを解放すると、意図した制限が台無しになる可能性があります。ストリーミングされるボディは、その後も長く接続を使い続けることがあります。ボディが消費されるか、アプリケーションのワークフローに従ってレスポンスが閉じられるまで、許可を保持します。

ネストされたコンテキストマネージャーは、例外やキャンセルが操作を離れるときのクリーンアップを可視化します。手動で取得し、成功した関数の末尾でのみ解放するのは避けてください。

接続制限は別の問いに答える

aiohttp のコネクタ制限は、コネクタの文書化された規則に従って接続を制限します。アプリケーションのセマフォと併用しても有用です。宛先ごとの接続制限は、その名前だけでは、1 つのプロキシに割り当てられたすべての宛先が共有するバジェットを表しません。

同時実行とリクエストレートも区別してください。4 件の長寿命リクエストと 4 件の非常に短いリクエストは同じ同時実行枠を使いますが、1 秒あたりのリクエストレートは異なります。レートポリシーを持つサービスでは、ペーシングや別個のレートリミッターが必要になる場合があります。

ログとメトリクスには、安定した秘密でないプロキシ識別子を選びます。資格情報を含む 2 つの URL が同じ容量プールを指すなら、1 つのバジェットを共有すべきか決めてください。逆に、異なるポートやアカウントが別々の制限を表すこともあります(運用者がそのように文書化している場合)。

スループットが低下したら、作業がどこで待っているかを調べます。自分のセマフォ、コネクタ、プロキシ接続の確立、レスポンスの読み取りのいずれかです。すべての制限を一度に引き上げると、その区別が難しくなり、キューが管理していないプロキシへ移る可能性があります。

関連記事