Why a simple loop can accidentally DDOS your DNS provider.
Domain Name System (DNS) turns a human-readable name like api.stripe.com into an IP address like 3.14.25.10. Every time you make an HTTP request using a domain name, the underlying OS has to do a DNS lookup over the network. If you place an HTTP request inside a tight for loop (e.g., fetching 10,000 user profiles), and your HTTP client doesn't reuse connections or cache DNS, you will execute 10,000 separate DNS lookups in a few seconds. This adds massive latency to your app and can get your server blacklisted by your DNS provider.
To fix this, you must use Connection Pooling (Keep-Alive) and DNS Caching. A Connection Pool keeps the underlying TCP connection open after the first request finishes. When the next iteration of the loop happens, the HTTP client skips the DNS lookup, skips the TCP handshake, and skips the TLS handshake. It just sends the data directly over the open pipe.
# THE BAD WAY (creates a new TCP connection & DNS lookup every time)
import requests
for user_id in range(100):
# 'requests.get' opens and closes a connection automatically
requests.get(f"https://api.example.com/users/{user_id}")
# THE GOOD WAY (Connection Pooling)
session = requests.Session() # Keeps the TCP connection alive
for user_id in range(100):
# Only the FIRST request does a DNS lookup.
# The next 99 requests reuse the exact same TCP pipe.
session.get(f"https://api.example.com/users/{user_id}")
Connection Pools consume memory on both your server and the target server. If you leave 10,000 Idle connections open forever, the target server will eventually crash from connection exhaustion. This is why Connection Pools have a max_idle_connections limit and an idle_timeout (e.g. they automatically close after 30 seconds of inactivity).