TCP TIME_WAIT Vyčerpanie Portov: Keď Connection Pooling Nestačí
V den, ked nam dosli porty, som konecne zacal respektovat TIME_WAIT. “Náhle sa nedá pripojiť k databáze - address already in use.” Príčina: vaša vysokopriepustná služba vyčerpala ephemeral porty lebo TCP TIME_WAIT drží sockety otvorené 60 sekúnd po zatvorení.
Prostredie: Vysokopriepustné služby, microservices s mnohými odchádzajúcimi spojeniami, zlá konfigurácia connection pool, krátkodobé HTTP spojenia
Problém
Záhadné Zlyhania Spojení
Časová os vyčerpania portov:
T+0:00 Služba beží normálne
Odchádzajúce spojenia na DB, cache, API
Ephemeral port range: 32768-60999 (28,231 portov)
T+0:10 Traffic spike - 1000 req/sek
Každý request robí 3 odchádzajúce volania
3000 spojení/sek otvorených a zatvorených
T+0:30 TIME_WAIT sockety sa hromadia
Každý zostáva 60 sekúnd
3000 × 60 = 180,000 socketov potrebných!
T+0:35 Error: cannot assign requested address
Všetky ephemeral porty k DB IP:port vyčerpané
Nové spojenia nemožné
Čo Je Vlastne TIME_WAIT
Životný Cyklus TCP Spojenia:
Klient Server
| |
|-------- SYN ------------------> |
|<------- SYN-ACK --------------- |
|-------- ACK ------------------> |
| ESTABLISHED |
|<======= DATA ==================> |
| |
|-------- FIN ------------------> | Klient iniciuje zatvorenie
|<------- ACK ------------------- |
|<------- FIN ------------------- |
|-------- ACK ------------------> |
| |
| TIME_WAIT (2 × MSL = 60s) | ← Socket nepoužiteľný!
| |
↓ Konečne zatvorený |
Prečo TIME_WAIT existuje:
1. Zabezpečiť že finálne ACK dorazí na server
2. Nechať duplikátne packety expirovať
3. Zabrániť starým packetom kazeniu nového spojenia
Problém: Vysoká priepustnosť = veľa TIME_WAITov
Príčina
Problém 4-Tuple
TCP socket identifikovaný 4-tuple:
(source_ip, source_port, dest_ip, dest_port)
Vaša služba: 10.0.1.50
Databáza: 10.0.2.100:5432
Dostupné kombinácie:
10.0.1.50:32768 → 10.0.2.100:5432
10.0.1.50:32769 → 10.0.2.100:5432
...
10.0.1.50:60999 → 10.0.2.100:5432
Len 28,231 unikátnych 4-tuples možných!
Pri 500 spojeniach/sek s 60s TIME_WAIT:
500 × 60 = 30,000 socketov potrebných
30,000 > 28,231 dostupných → VYČERPANIE
Skontrolujte Stav Vašich Socketov
# Počet socketov podľa stavu
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn
# Výstup:
# 24567 TIME-WAIT
# 2341 ESTABLISHED
# 234 LISTEN
# 45 FIN-WAIT-2
# TIME_WAIT na konkrétnu destináciu
ss -tan state time-wait | grep "10.0.2.100:5432" | wc -l
# 23456 ← Takmer všetky porty použité!
# Kontrola ephemeral port range
cat /proc/sys/net/ipv4/ip_local_port_range
# 32768 60999
Skutočná Matematika
# Výpočet maximálnej udržateľnej rýchlosti
# Ephemeral porty: 60999 - 32768 = 28,231
# TIME_WAIT trvanie: 60 sekúnd
# Max nových spojení/sek: 28,231 / 60 = 470/sek na destináciu
# Ak máte 10 unikátnych destinácií:
# Max celkom: 4,700 nových spojení/sek
# Ale ak všetky idú na JEDNU destináciu (vašu DB):
# Max: 470 spojení/sek udržateľne
# Reality check vášho workloadu:
ss -tan state time-wait dst 10.0.2.100:5432 | wc -l
# Ak blízko 28,231 → ste na limite
Diagnostika
Identifikujte Bottleneck
# Nájdite top TIME_WAIT destinácie
ss -tan state time-wait | awk '{print $4}' | sort | uniq -c | sort -rn | head
# 23456 10.0.2.100:5432 ← Databáza
# 3421 10.0.3.50:6379 ← Redis
# 1234 10.0.4.100:80 ← API služba
# Ak jedna destinácia dominuje → to je váš bottleneck
# Kontrola obchádzania connection pool
netstat -an | grep "10.0.2.100:5432" | grep -c ESTABLISHED
# Malo by byť stabilné (veľkosť poolu), nie kolísať
Diagnostika na Úrovni Aplikácie
# Trace vytvárania spojení (Java príklad)
jcmd <pid> VM.native_memory summary | grep -A5 "Internal"
# Kontrola HikariCP pool stats
curl localhost:8080/actuator/metrics/hikaricp.connections.active
curl localhost:8080/actuator/metrics/hikaricp.connections.idle
# Ak vytvorených spojení > veľkosť poolu
# → Pool je obchádzaný alebo zle nakonfigurovaný
Riešenie
Možnosť 1: Znovupoužitie Spojení (Najlepšie)
# HikariCP - správne dimenzovanie poolu
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 10
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
# KĽÚČ: Nevytvárať nové spojenia pre každý query!
// Go - konfigurácia connection pool
db, _ := sql.Open("postgres", connStr)
db.SetMaxOpenConns(20)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(30 * time.Minute)
db.SetConnMaxIdleTime(10 * time.Minute)
# Python - SQLAlchemy pool
engine = create_engine(
"postgresql://...",
pool_size=20,
max_overflow=10,
pool_pre_ping=True,
pool_recycle=1800
)
Možnosť 2: HTTP Keep-Alive
// Go HTTP klient - znovupoužitie spojení
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
// Znovupoužitie TCP spojení pre viaceré requesty
},
}
// ZLE: Vytváranie nového klienta per request
func badHandler(w http.ResponseWriter, r *http.Request) {
client := &http.Client{} // Nový klient = nové spojenia!
resp, _ := client.Get("http://api/endpoint")
}
// SPRÁVNE: Znovupoužitie klienta
var httpClient = &http.Client{...} // Package-level
func goodHandler(w http.ResponseWriter, r *http.Request) {
resp, _ := httpClient.Get("http://api/endpoint")
}
Možnosť 3: TCP Tuning (Opatrne!)
# Rozšírenie ephemeral port range
echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range
# Teraz 64,511 portov namiesto 28,231
# Povolenie TIME_WAIT reuse (vyžaduje timestamps)
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
# Umožňuje znovupoužitie TIME_WAIT socketov pre nové odchádzajúce spojenia
# BEZPEČNÉ pre client-side spojenia
# VAROVANIE: tcp_tw_recycle je NEBEZPEČNÉ a odstránené v Linux 4.12
# Rozbíja spojenia cez NAT
# NIKDY nepoužívajte tcp_tw_recycle
# Zníženie TIME_WAIT trvania (neodporúča sa)
# Linux nepodporuje priamu zmenu
# Vyžadovalo by rekompláciu kernelu
# Kubernetes sysctl tuning
apiVersion: v1
kind: Pod
spec:
securityContext:
sysctls:
- name: net.ipv4.ip_local_port_range
value: "1024 65535"
- name: net.ipv4.tcp_tw_reuse
value: "1"
Možnosť 4: Viaceré Zdrojové IP
# Ak sa pripájate na jednu destináciu, pridajte zdrojové IP
# Každá zdrojová IP dostane vlastný port range
ip addr add 10.0.1.51/24 dev eth0
ip addr add 10.0.1.52/24 dev eth0
# Nakonfigurujte aplikáciu na rotáciu zdrojových IP
# Efektívny port range znásobený počtom IP
// Go - bind na konkrétnu zdrojovú IP
dialer := &net.Dialer{
LocalAddr: &net.TCPAddr{
IP: net.ParseIP("10.0.1.51"),
},
}
transport := &http.Transport{
DialContext: dialer.DialContext,
}
Možnosť 5: SO_LINGER (Posledná Možnosť)
// Vynútenie okamžitého zatvorenia socketu (nebezpečné!)
conn, _ := net.Dial("tcp", "10.0.2.100:5432")
tcpConn := conn.(*net.TCPConn)
// Nastavenie linger na 0 = poslať RST namiesto FIN
// Vyhne sa TIME_WAIT ale môže stratiť dáta!
tcpConn.SetLinger(0)
tcpConn.Close()
// VAROVANIE: Toto môže spôsobiť:
// - Stratu dát ak send buffer nie je prázdny
// - Server dostane RST (connection reset)
// - Použite len pre read-only spojenia alebo nekritické
Monitoring
groups:
- name: tcp-exhaustion
rules:
- alert: TimeWaitSocketsHigh
expr: |
node_sockstat_TCP_tw > 20000
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $value }} socketov v TIME_WAIT"
- alert: EphemeralPortsLow
expr: |
(node_sockstat_TCP_tw + node_sockstat_TCP_alloc) /
(node_nf_conntrack_entries_limit) > 0.8
for: 5m
labels:
severity: critical
annotations:
summary: "Vyčerpanie ephemeral portov blízko"
- alert: ConnectionPoolBypass
expr: |
rate(hikaricp_connections_creation_seconds_count[5m]) > 10
for: 10m
labels:
severity: warning
annotations:
summary: "Vysoká rýchlosť vytvárania spojení - pool môže byť obchádzaný"
# Rýchly monitoring script
watch -n 5 'echo "TIME_WAIT:"; ss -tan state time-wait | wc -l;
echo "ESTABLISHED:"; ss -tan state established | wc -l;
echo "Top destinácie:"; ss -tan state time-wait | awk "{print \$4}" | sort | uniq -c | sort -rn | head -5'
Checklist
## TCP TIME_WAIT Vyčerpanie Portov
### Diagnostika
- [ ] Spočítajte TIME_WAIT sockety: ss -tan state time-wait | wc -l
- [ ] Identifikujte top destinácie podľa počtu TIME_WAIT
- [ ] Skontrolujte ephemeral port range: cat /proc/sys/net/ipv4/ip_local_port_range
- [ ] Vypočítajte max udržateľnú rýchlosť na destináciu
### Opravy Aplikácie (Robte Prvé)
- [ ] Overte že connection pool je nakonfigurovaný
- [ ] Skontrolujte že pool sa skutočne používa (nie je obchádzaný)
- [ ] Povoľte HTTP keep-alive pre API volania
- [ ] Znovupoužívajte HTTP klientov naprieč requestami
### Systémový Tuning (Ak Potrebný)
- [ ] Rozšírte ephemeral port range: 1024-65535
- [ ] Povoľte tcp_tw_reuse (bezpečné pre klientov)
- [ ] Zvážte viaceré zdrojové IP pre jednu destináciu
- [ ] NIKDY nepoužívajte tcp_tw_recycle
### Monitoring
- [ ] Alertujte na počet TIME_WAIT socketov
- [ ] Alertujte na rýchlosť vytvárania spojení
- [ ] Monitorujte využitie port range
Záver
Poučenie: TCP TIME_WAIT je funkcia, nie bug - ale vysokopriepustné služby môžu vyčerpať ephemeral porty ak spojenia nie sú správne poolované a znovupoužívané.
Kľúčové princípy:
- Connection pooling je riešenie - znovupoužívajte spojenia, nevytvárajte nové
- TIME_WAIT existuje pre bezpečnosť - neeliminujte ho, obíďte ho
- tcp_tw_reuse je bezpečné - pre client-side spojenia s timestamps
- tcp_tw_recycle je nebezpečné - odstránené z moderných kernelov z dobrého dôvodu
Súvisiace Články
- Database Connection Pool Exhaustion - Problémy s konfiguráciou poolu
- Kubernetes Service Connection Issues - K8s networking problémy
Súvisiace články
tcpdump vidí SYN, ale služba timeoutuje: pasca listen backlogu
Klienti timeoutujú, tcpdump ukazuje SYN (niekedy aj SYN-ACK), ale aplikácia nič neloguje. Častý vinník: Linux listen/accept fronty, ktoré sa pri load-e alebo CPU starvation preplnia.
kube-proxy Mikro-Výpadky: Problém xtables Lock Contencie
Náhodné 1-3 sekundové výpadky spojení počas deploymentov. CPU vyzerá v poriadku, pamäť stabilná. Skrytá príčina: iptables-restore drží xtables lock počas endpoint churnu.
PMTU Blackholes: Keď Iba Veľké Odpovede Visia
Malé API odpovede fungujú, veľké visia navždy. Príčina: ICMP 'Fragmentation Needed' správy filtrované firewallmi, rozbíjajú Path MTU Discovery v overlay sieťach.
Vyčerpanie Ephemeral Portov: Node Ktorý 'Pokazí'
Jeden Kubernetes node začne zlyhávať pripojenia k externým službám zatiaľ čo pody vyzerajú zdravé. Skrytá príčina: sidecar proxy vyčerpávajú ephemeral porty krátkodobými spojeniami.
Citujte tento článok
Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.