Performance Considerations for Concurrently Checking 120+ Websites with Holehe
Holehe achieves high-throughput email verification across hundreds of sites by using Trio's structured concurrency with a shared HTTPX async client, but optimal performance requires tuning connection limits, timeouts, and module selection.
When scanning an email address against 120 or more websites, understanding Holehe's concurrency model is essential for maximizing speed without triggering rate limits. This guide examines the architectural decisions in megadose/holehe that affect performance and provides actionable optimizations based on the source code implementation.
How Holehe Handles Concurrent Requests
Holehe's concurrency engine resides in holehe/core.py. The tool orchestrates all site checks through Trio nurseries and a shared asynchronous HTTP client.
The Async Client Architecture
In core.py:L13-L14, Holehe instantiates a single httpx.AsyncClient:
client = httpx.AsyncClient(timeout=timeout)
This single client is passed to every module, enabling connection reuse across all 120+ site checks. The HTTPX default limits (approximately 100 concurrent connections) cap how many simultaneous TCP handshakes occur.
To increase throughput in a custom fork, override these defaults:
import httpx
limits = httpx.Limits(
max_keepalive_connections=200,
max_connections=200
)
client = httpx.AsyncClient(timeout=10, limits=limits)
Trio Nursery Task Dispatch
The parallel execution happens in core.py:L18-L21:
async with trio.open_nursery() as n:
for function in get_functions(modules, args):
n.start_soon(launch_module, function, args.email, client, out)
Every module starts immediately as a separate coroutine. With 120+ websites, this creates 120+ concurrent tasks competing for the event loop's attention and the HTTP connection pool.
Key Bottlenecks When Scaling to 120+ Sites
Connection Pool Saturation
With HTTPX defaults, the 101st concurrent request queues until a connection frees. For 120 sites, this creates minor queuing; for 300 (Holehe's full module set), it becomes significant.
Symptoms: Progress bar moves in batches rather than steadily.
Mitigation: Filter modules to only necessary sites using --no-password-recovery (-NP) or a custom module list.
Timeout Configuration
The --timeout parameter (core.py:L94-L96) bounds each request:
# Aggressive timeout for fast networks
holehe user@example.com -T 5
# Generous timeout for VPN or high-latency paths
holehe user@example.com -T 20
A single hanging site with a 10-second default timeout delays result collection for that slot. With 120 sites, multiple slow endpoints compound this effect.
Rate Limiting and 429 Responses
Modules return rateLimit=True when detecting HTTP 429 or equivalent (core.py:L22-L26). Holehe continues other tasks, but the underlying TCP connection was already consumed.
Detection: Use --no-clear to preserve the progress bar and watch for stalls.
Response: Consider a custom wrapper with controlled delays:
import trio
async def throttled_launch(function, email, client, out):
await trio.sleep(0.1) # 100ms stagger between starts
await launch_module(function, email, client, out)
CPU and Memory Characteristics
Holehe's workload is I/O-bound, not CPU-bound. Typical CPU usage stays below 5% on modern hardware because the event loop spends nearly all time awaiting network responses.
Implications:
- Adding CPU-intensive parsing (e.g., complex HTML extraction) disrupts this balance
- Memory usage scales with the number of concurrent responses held in memory
- The
TrioProgressinstrument (instruments.py:L8-L10) adds negligible overhead viatqdmupdates
Practical Optimization Strategies
1. Reduce Module Count
Holehe discovers ~300 modules by default via import_submodules("holehe.modules") (core.py:L36-L47). For 120-site targets, explicitly select relevant modules:
from holehe.core import import_submodules, get_functions
modules = import_submodules("holehe.modules")
# Filter to specific sites
target_sites = ['github', 'twitter', 'instagram', 'linkedin']
filtered = {k: v for k, v in modules.items() if any(t in k for t in target_sites)}
2. Monitor with Instruments
The TrioProgress class provides real-time visibility. A flat progress bar indicates mass rate-limiting or network congestion.
holehe user@example.com --no-clear
3. Programmatic Control
For production scanning, replicate Holehe's core logic with custom constraints:
import httpx
import trio
from holehe.core import import_submodules, launch_module
from holehe.instruments import TrioProgress
async def controlled_scan(email, sites, max_concurrent=50, timeout=10):
modules = import_submodules("holehe.modules")
funcs = [
modules[m].__dict__[m.split(".")[-1]]
for m in modules
if m.split(".")[-1] in sites
]
client = httpx.AsyncClient(
timeout=timeout,
limits=httpx.Limits(max_connections=max_concurrent)
)
out = []
instrument = TrioProgress(len(funcs))
trio.lowlevel.add_instrument(instrument)
# Use semaphore for controlled concurrency
sem = trio.Semaphore(max_concurrent)
async def bounded_launch(fn):
async with sem:
await launch_module(fn, email, client, out)
async with trio.open_nursery() as n:
for fn in funcs:
n.start_soon(bounded_launch, fn)
trio.lowlevel.remove_instrument(instrument)
await client.aclose()
return out
Performance Checklist for 120+ Sites
| Factor | Default | Recommended for 120+ Sites |
|---|---|---|
| HTTPX connections | ~100 | 150-200 if customizing |
| Timeout | 10s | 5-15s based on network |
| Modules loaded | ~300 | Filter to 50-150 relevant |
| Stagger/delay | None | 50-100ms if rate-limited |
| Progress visibility | Clearing | Use --no-clear |
Summary
- Holehe uses a shared HTTPX async client and Trio nurseries for concurrent site checking, with architecture defined in
holehe/core.py - Connection pool defaults (~100) may bottleneck 120+ simultaneous requests; customize
httpx.Limitsfor higher throughput - Every module starts as an immediate coroutine, so reducing the module count via filtering or
-NPdirectly improves performance - Timeout tuning (
-T) balances completeness against speed—aggressive values suit fast networks, generous values suit VPNs - Rate limiting manifests as progress bar stalls; use
--no-clearto detect and consider custom delays for throttled targets - CPU usage remains minimal; focus optimizations on I/O concurrency and network behavior
Frequently Asked Questions
How many concurrent connections does Holehe use by default?
Holehe relies on HTTPX's default connection limits, approximately 100 concurrent keepalive connections. For 120+ sites, this creates mild queuing. The shared client is instantiated at core.py:L13-L14 without explicit limits, so the final behavior depends on HTTPX's internal defaults for your version.
Can I limit how many sites run simultaneously?
Holehe does not expose a built-in concurrency limiter. All modules start immediately via nursery.start_soon() in core.py:L18-L21. To enforce limits, use a trio.Semaphore wrapper as shown in the programmatic example above, or reduce the module count through filtering.
Why does Holehe slow down after 100-150 sites?
The progress bar often stalls when rate limits trigger across multiple targets. Each 429 response consumes a connection slot and delays result collection. Check for this pattern with --no-clear and consider targeting fewer sites or adding inter-request delays.
Is Holehe suitable for scanning thousands of email addresses?
Holehe is optimized for breadth (many sites) over volume (many emails). For large email lists, wrap Holehe calls with your own orchestration that respects remote rate limits, or implement backoff strategies in custom modules to avoid IP bans.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →