<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[The Proxy Layer]]></title><description><![CDATA[The Proxy Layer]]></description><link>https://proxy-layer.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>The Proxy Layer</title><link>https://proxy-layer.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 09 Sep 2026 19:04:27 GMT</lastBuildDate><atom:link href="https://proxy-layer.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a Resilient Proxy Pool Manager in Python]]></title><description><![CDATA[A proxy pool manager's job is simple: hand out a working proxy, notice when one stops working, and stop handing that one out for a while.
Most homemade pools skip the second and third parts. They just]]></description><link>https://proxy-layer.hashnode.dev/resilient-proxy-pool-manager-python</link><guid isPermaLink="true">https://proxy-layer.hashnode.dev/resilient-proxy-pool-manager-python</guid><category><![CDATA[Python]]></category><category><![CDATA[web scraping]]></category><category><![CDATA[proxies]]></category><category><![CDATA[backend]]></category><category><![CDATA[automation]]></category><dc:creator><![CDATA[ethancartertech]]></dc:creator><pubDate>Tue, 08 Sep 2026 11:48:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a32739e9accb1e8bdabd8b1/7fae51c9-9acb-4486-be49-de2a8b82c8f0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A proxy pool manager's job is simple: hand out a working proxy, notice when one stops working, and stop handing that one out for a while.</p>
<p>Most homemade pools skip the second and third parts. They just round-robin through a list and burn requests on dead IPs until someone notices the failure rate. Here's a pattern that fixes that, in plain Python with no extra dependencies beyond <code>requests</code>.</p>
<h2>The core idea</h2>
<p>Every proxy gets a small health record: how many times it's failed in a row, and when it's allowed back into rotation after being marked bad.</p>
<pre><code class="language-python">import time
import random
import requests

class Proxy:
    def __init__(self, address):
        self.address = address
        self.fail_count = 0
        self.cooldown_until = 0

    def is_available(self):
        return time.time() &gt;= self.cooldown_until

    def mark_success(self):
        self.fail_count = 0

    def mark_failure(self):
        self.fail_count += 1
        # Exponential backoff: 30s, 60s, 120s, 240s...
        cooldown_seconds = 30 * (2 ** (self.fail_count - 1))
        self.cooldown_until = time.time() + min(cooldown_seconds, 1800)
</code></pre>
<p>Each failure pushes the cooldown further out, capped at 30 minutes. A proxy that fails once gets a short timeout. A proxy that keeps failing gets pushed further and further to the back of the line without being permanently removed, it might recover.</p>
<h2>The pool manager</h2>
<pre><code class="language-python">class ProxyPool:
    def __init__(self, addresses):
        self.proxies = [Proxy(addr) for addr in addresses]

def get_proxy(self):
    available = [p for p in self.proxies if p.is_available()]
    if not available:
        raise RuntimeError("No proxies available — all in cooldown")
    return random.choice(available)

def request(self, url, **kwargs):
    proxy = self.get_proxy()
    try:
        response = requests.get(
            url,
            proxies={"http": proxy.address, "https": proxy.address},
            timeout=10,
            **kwargs
        )
        if response.status_code &gt;= 500 or response.status_code == 429:
            proxy.mark_failure()
        else:
            proxy.mark_success()
        return response
    except requests.exceptions.RequestException:
        proxy.mark_failure()
        raise
</code></pre>
<p><code>get_proxy()</code> only pulls from proxies that are currently out of cooldown. <code>request()</code> wraps the actual call, marking success or failure based on what comes back, including a distinct check for <code>429</code>, since a rate-limit response means the proxy is fine but overused, not broken.</p>
<h2>Why random selection instead of round-robin</h2>
<p>Round-robin looks predictable to a target site: proxy A, then B, then C, then A again, on a fixed clock. Random selection from the available pool breaks that pattern without adding complexity.</p>
<p>If you need weighted selection later, favoring proxies with lower fail counts, for example, that's a small change to <code>get_proxy()</code>, not a rewrite.</p>
<h2>Handling total pool exhaustion</h2>
<p>If every proxy is in cooldown at once, <code>get_proxy()</code> raises instead of silently hanging. That's deliberate. A silent retry loop against an empty pool just burns CPU and time. Better to fail loud, log it, and let the caller decide whether to wait, alert, or fall back to a different pool.</p>
<pre><code class="language-python">try:
    response = pool.request("https://example.com/data")
except RuntimeError:
    print("Pool exhausted — pausing before retry")
    time.sleep(60)
</code></pre>
<h2>Where this goes from here</h2>
<p>This version tracks failure and cooldown per proxy, which covers most small-to-medium scraping jobs. At higher volume, the next additions are usually:</p>
<ul>
<li><p>Persisting proxy health to Redis so multiple workers share one pool state</p>
</li>
<li><p>Splitting the pool by target domain, since a proxy can be healthy for one site and banned on another</p>
</li>
<li><p>Swapping the fixed backoff formula for one tuned to your actual failure patterns</p>
</li>
</ul>
<p>The pattern doesn't change. You're still just tracking who's healthy, who isn't, and how long the unhealthy ones sit out. Providers that expose large datacenter proxy blocks, like <a href="https://instantproxies.com/">InstantProxies</a>, give this kind of pool manager enough addresses to make the cooldown math actually work: a pool of five proxies doesn't have room to rotate anyone out.</p>
]]></content:encoded></item></channel></rss>