
2023년 초, 주문 처리 시스템에서 간헐적으로 DB 연결 실패 현상이 발생했습니다.
특히 트래픽이 급증하는 점심시간대(11:30~13:30)에 주문 실패율이 평상시 0.1%에서 최대 5%까지 증가하는 현상이 발생했습니다.
에러 로그
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException()
먼저 DB 서버의 리소스 사용량을 확인했습니다.
// DB 메트릭 수집 코드
@Scheduled(fixedRate = 60000)
public void collectMetrics() {
try (Connection conn = dataSource.getConnection()) {
try (Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery(
"SELECT COUNT(*) as connections FROM information_schema.processlist"
);
if (rs.next()) {
meterRegistry.gauge("db.connections.active", rs.getInt("connections"));
}
}
}
}
네트워크 패킷 분석 도구(tcpdump)를 사용하여 문제를 추적한 결과, NACL의 Ephemeral Port 설정이 잘못되어 있었습니다.
# 패킷 캡처 명령어
tcpdump -i any port 3306 -w capture.pcap
문제가 되는 NACL 설정
{
"inbound_rules": [
{
"port_range": "3306",
"protocol": "tcp",
"allow": true
}
],
"outbound_rules": [
{
"port_range": "1024-32768", // 문제의 설정
"protocol": "tcp",
"allow": true
}
]
}
리눅스 서버의 Ephemeral Port 범위(32768-60999)와 NACL 설정이 불일치하여 일부 응답 패킷이 차단되고 있었습니다.
{
"inbound_rules": [
{
"port_range": "3306",
"protocol": "tcp",
"allow": true
}
],
"outbound_rules": [
{
"port_range": "32768-60999", // 수정된 설정
"protocol": "tcp",
"allow": true
}
]
}
HikariCP 설정도 함께 최적화했습니다.
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 300000
connection-timeout: 20000
validation-timeout: 5000

문제의 재발을 방지하기 위해 Prometheus + Grafana 기반의 모니터링 시스템을 구축했습니다
@Configuration
public class MetricsConfig {
@Bean
MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
@Bean
DatabaseMetrics databaseMetrics(DataSource dataSource, MeterRegistry registry) {
return new DatabaseMetrics(dataSource, registry);
}
}
네트워크 설정, 특히 NACL의 Ephemeral Port 설정은 서비스의 안정성에 큰 영향을 미칩니다.
문제 해결을 위해서는 애플리케이션 레벨부터 인프라 레벨까지 종합적인 분석이 필요합니다.
모니터링 시스템 구축의 중요성을 다시 한번 확인했습니다.
#!/bin/bash
# network-validation.sh
# 현재 시스템의 Ephemeral Port 범위 확인
EPHEMERAL_RANGE=$(cat /proc/sys/net/ipv4/ip_local_port_range)
# NACL 설정 가져오기
AWS_NACL_RANGE=$(aws ec2 describe-network-acls --network-acl-ids $NACL_ID --query 'NetworkAcls[0].Entries[?PortRange].PortRange' --output text)
# 범위 비교 및 검증
validate_port_range() {
local system_range=$1
local nacl_range=$2
# 검증 로직 구현
echo "Validating port ranges..."
}
# 주기적인 검증 실행
validate_port_range "$EPHEMERAL_RANGE" "$AWS_NACL_RANGE"
@Component
@Slf4j
public class ConnectionPoolMonitor {
private final HikariDataSource dataSource;
private final MeterRegistry meterRegistry;
@Scheduled(fixedRate = 5000)
public void monitorConnectionPool() {
HikariPoolMXBean poolMXBean = dataSource.getHikariPoolMXBean();
// 활성 연결 수 모니터링
meterRegistry.gauge("hikari.active.connections",
poolMXBean.getActiveConnections());
// 대기 시간 모니터링
meterRegistry.gauge("hikari.waiting.threads",
poolMXBean.getThreadsAwaitingConnection());
// Connection 획득 시간 분포 모니터링
Timer.builder("hikari.connection.acquisition.time")
.publishPercentiles(0.5, 0.95, 0.99)
.register(meterRegistry);
}
}
@SpringBootTest
class DatabaseConnectionTest {
@Autowired
private DataSource dataSource;
@Test
void highConcurrencyTest() {
int threadCount = 100;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CountDownLatch latch = new CountDownLatch(threadCount);
IntStream.range(0, threadCount).forEach(i -> {
executor.submit(() -> {
try (Connection conn = dataSource.getConnection()) {
// DB 작업 시뮬레이션
Thread.sleep(new Random().nextInt(1000));
} catch (Exception e) {
log.error("Connection error", e);
} finally {
latch.countDown();
}
});
});
assertTrue(latch.await(30, TimeUnit.SECONDS));
}
}
import re
from collections import defaultdict
def analyze_connection_logs(log_file):
error_patterns = {
'timeout': r'connection timeout',
'refused': r'connection refused',
'reset': r'connection reset'
}
error_counts = defaultdict(int)
with open(log_file, 'r') as f:
for line in f:
for error_type, pattern in error_patterns.items():
if re.search(pattern, line, re.I):
error_counts[error_type] += 1
return error_counts
# .github/workflows/network-validation.yml
name: Network Configuration Validation
on:
push:
paths:
- 'infrastructure/**'
- 'network/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Validate Network Settings
run: ./scripts/network-validation.sh
- name: Test Database Connectivity
run: ./scripts/db-connectivity-test.sh