When we migrated our production systems from physical to virtual infrastructure, we encountered an unexpected TCP socket issue. Our application, which creates short-lived MySQL connections (one per query), suddenly started experiencing intermittent connection problems. Network analysis revealed 24,000+ sockets stuck in TIME_WAIT state - significantly higher than the 17,000 we observed on physical hardware.
In TCP/IP networking, TIME_WAIT is a normal state that ensures proper connection termination. When a connection closes, the socket remains in TIME_WAIT for a period (typically 60 seconds in Linux) to handle any delayed packets. The formula for maximum TIME_WAIT sockets is roughly:
max_sockets = (local_port_range_size) * (number_of_remote_ips) * (number_of_remote_ports)
In virtualized environments, factors like reduced ephemeral port ranges and NAT can exacerbate this issue.
Setting /proc/sys/net/ipv4/tcp_tw_reuse
to 1 allows the kernel to reuse TIME_WAIT sockets for new connections when it's safe from a protocol perspective. This is generally considered safer than tw_recycle. The risks include:
- Potential connection issues with stateful firewalls
- Very small risk of segment duplication in edge cases
To enable it:
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
# Or make persistent:
echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf
sysctl -p
The tcp_tw_recycle
option was removed in Linux 4.12+ because it could cause serious problems in NAT environments. It violated RFC standards by aggressively cleaning up TIME_WAIT sockets and could drop legitimate connections.
For MySQL specifically, consider these architectural improvements:
// Connection pooling example in Python
import mysql.connector
from mysql.connector import pooling
dbconfig = {
"database": "test",
"user": "user"
}
connection_pool = mysql.connector.pooling.MySQLConnectionPool(
pool_name = "mypool",
pool_size = 5,
**dbconfig
)
def query(sql):
conn = connection_pool.get_connection()
cursor = conn.cursor()
cursor.execute(sql)
result = cursor.fetchall()
cursor.close()
conn.close() # Returns to pool
return result
Other system-level tweaks:
# Reduce TIME_WAIT timeout (default 60s)
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
# Increase local port range
echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range
VM environments often have additional constraints:
- Hypervisor network stacks may impose their own connection limits
- VirtIO drivers may require tuning (
ethtool -K eth0 tx-checksumming off
) - Check for TCP segmentation offload issues
For VMware specifically, these ESXi settings may help:
esxcli system settings advanced set -o /Net/TcpipHeapSize -i 30
esxcli system settings advanced set -o /Net/TcpipHeapMax -i 1536
When working with high-frequency database connections in Linux environments, you'll inevitably encounter the TCP TIME_WAIT state. This occurs when a connection is closed, but the socket remains in memory for 2*MSL (Maximum Segment Lifetime) - typically 60 seconds in Linux - to ensure all packets in transit are properly handled.
In your case with virtualized MySQL clients, we're seeing up to 24,000+ sockets stuck in TIME_WAIT. While this isn't technically a memory leak (each socket consumes about 1KB), it can lead to:
- Port exhaustion (limited by net.ipv4.ip_local_port_range)
- Connection delays or failures
- Increased system resource usage
Your observation about different behavior between physical and virtual hosts is insightful. Virtual environments often show this issue more prominently due to:
# Check current TIME_WAIT connections
ss -tan | grep TIME-WAIT | wc -l
# Compare with physical host baseline (17000 in your case)
Virtualization adds network abstraction layers that can affect TCP timings. Additionally, VM migration might have reset some TCP stack optimizations present in your physical host.
Setting net.ipv4.tcp_tw_reuse = 1
is generally safe for your use case. This allows the kernel to reuse TIME_WAIT sockets for new connections when:
- The new connection's timestamp > the last packet received on the old connection
- It's safe from old duplicate packets being misinterpreted
# Temporary setting (for testing)
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
# Permanent setting (add to /etc/sysctl.conf)
echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf
sysctl -p
The main danger would be in environments with strict packet ordering requirements or where you can't guarantee timestamp options are enabled (they are by default in modern kernels).
While tcp_tw_recycle
seems tempting (it aggressively recycles sockets), it's problematic because:
- It can break NAT environments (common in virtualization)
- Relies on TCP timestamps which might not be synchronized
- Was removed entirely in Linux 4.12+
# Never use in production!
echo 0 > /proc/sys/net/ipv4/tcp_tw_recycle
While tcp_tw_reuse helps, consider these MySQL-specific optimizations:
# Connection pooling configuration example (PHP/PDO)
$options = [
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
];
# Adjusting kernel parameters
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout # Reduce FIN timeout
echo "net.ipv4.tcp_max_tw_buckets = 180000" >> /etc/sysctl.conf # Increase buckets
After implementing changes, monitor with:
# Real-time socket monitoring
watch -n 1 'ss -s | grep TIME-WAIT'
# Network stack statistics
cat /proc/net/sockstat
In production, gradually implement changes and monitor for any connection issues, especially during peak loads.