[aws] NACL 설정 오류로 인한 간헐적 DB 연결 실패 문제 해결기

궁금하면 500원·2025년 1월 2일
0

데브옵스

목록 보기
29/36

1. 문제 상황

2023년 초, 주문 처리 시스템에서 간헐적으로 DB 연결 실패 현상이 발생했습니다.

특히 트래픽이 급증하는 점심시간대(11:30~13:30)에 주문 실패율이 평상시 0.1%에서 최대 5%까지 증가하는 현상이 발생했습니다.

증상

  • 간헐적인 DB 연결 타임아웃 발생
  • Connection Pool 고갈
  • 주문 처리 지연 및 실패

에러 로그

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()

2. 원인 분석

2.1 초기 가설

  • DB 서버 리소스 부족
  • Connection Pool 설정 최적화 필요
  • 네트워크 설정 이슈

2.2 분석 과정

먼저 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"));
            }
        }
    }
}

분석 결과

  • CPU 사용률: 평균 30% (정상)
  • 메모리 사용률: 평균 60% (정상)
  • Disk I/O: 정상 범위
  • Active Connections: 최대 치솟을 때도 설정된 최대값(1000)의 절반 수준

2.3 실제 원인 발견

네트워크 패킷 분석 도구(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 설정이 불일치하여 일부 응답 패킷이 차단되고 있었습니다.

3. 해결 방안

3.1 NACL 설정 수정

{
    "inbound_rules": [
        {
            "port_range": "3306",
            "protocol": "tcp",
            "allow": true
        }
    ],
    "outbound_rules": [
        {
            "port_range": "32768-60999",  // 수정된 설정
            "protocol": "tcp",
            "allow": true
        }
    ]
}

3.2 Connection Pool 최적화

HikariCP 설정도 함께 최적화했습니다.

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      idle-timeout: 300000
      connection-timeout: 20000
      validation-timeout: 5000

4. 개선 결과

4.1 성능 측정 데이터

4.2 모니터링 시스템 구축

문제의 재발을 방지하기 위해 Prometheus + Grafana 기반의 모니터링 시스템을 구축했습니다

@Configuration
public class MetricsConfig {
    @Bean
    MeterRegistry meterRegistry() {
        return new SimpleMeterRegistry();
    }
    
    @Bean
    DatabaseMetrics databaseMetrics(DataSource dataSource, MeterRegistry registry) {
        return new DatabaseMetrics(dataSource, registry);
    }
}

5. 결론 및 교훈

네트워크 설정, 특히 NACL의 Ephemeral Port 설정은 서비스의 안정성에 큰 영향을 미칩니다.
문제 해결을 위해서는 애플리케이션 레벨부터 인프라 레벨까지 종합적인 분석이 필요합니다.
모니터링 시스템 구축의 중요성을 다시 한번 확인했습니다.

6. 후속 조치

  • 네트워크 설정 검증을 위한 자동화 테스트 도입
  • 인프라 설정 변경에 대한 리뷰 프로세스 강화
  • 장애 대응 매뉴얼 업데이트

7. 장애 예방을 위한 체크리스트

7.1 인프라 설정 검증

#!/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"

7.2 Connection Pool 모니터링 강화

@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);
    }
}

7.3 부하 테스트 시나리오

@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));
    }
}

8. 트러블슈팅 가이드

8.1 문제 발생시 빠른 대응을 위한 체크리스트

1. 즉시 확인할 항목

  • 현재 활성 연결 수
  • 대기중인 쿼리 수
  • 네트워크 ACL 로그
  • VPC Flow 로그

2. 임시 조치 방안

  • Connection Pool 크기 일시적 확대
  • 장애 도메인 격리
  • 읽기 전용 복제본으로 읽기 트래픽 우회

8.2 로그 분석 스크립트

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

9. CI/CD 파이프라인 통합

# .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
profile
꾸준히, 의미있는 사이드 프로젝트 경험과 문제해결 과정을 기록하기 위한 공간입니다.

0개의 댓글