PRO DBDATA PRO DBDATA

AWS RDS 데이터베이스 장애 조치 후 연결 지연 원인: DNS 캐시와 커넥션 풀 진단 가이드

읽는 시간 약 10분
aws rds eb8db0ec9db4ed84b0ebb2a0ec9db4ec8aa4 ec9ea5ec95a0 eca1b0ecb998 ed9b84 ec97b0eab2b0 eca780ec97b0 ec9b90ec9db8 dns ecba90ec8b9cec9980 1787467230

AWS RDS의 Multi-AZ 구성을 사용하면 데이터베이스 장애가 발생했을 때 대기 인스턴스가 새로운 기본 인스턴스로 승격된다. 애플리케이션은 기존과 동일한 RDS 엔드포인트를 사용하므로 장애 조치가 끝나면 자연스럽게 연결이 복구될 것으로 생각하기 쉽다.

하지만 실제 운영에서는 RDS 콘솔상 장애 조치가 완료됐는데도 일부 서버만 연결 오류가 계속되거나, 애플리케이션을 재시작한 후에야 정상화되는 상황이 발생한다. 이러한 현상은 데이터베이스 자체보다 애플리케이션과 운영체제에 남아 있는 DNS 캐시, 이미 끊어진 커넥션을 보관한 커넥션 풀, 재연결 정책이 복합적으로 작용해 발생하는 경우가 많다.

RDS 장애 조치가 완료돼도 연결이 즉시 복구되지 않는 이유

RDS Multi-AZ 장애 조치가 발생하면 AWS는 대기 인스턴스를 새로운 기본 인스턴스로 전환하고 RDS 엔드포인트의 DNS 정보를 변경한다. 엔드포인트 이름은 그대로지만 실제 접속 대상의 IP 주소는 달라질 수 있다. 따라서 기존 데이터베이스 세션은 끊어지며 애플리케이션은 변경된 주소를 다시 조회해 새로운 연결을 생성해야 한다.

문제는 애플리케이션이 매번 DNS 서버에 질의하지 않는다는 점이다. JVM, 운영체제, 컨테이너의 DNS 리졸버, Kubernetes CoreDNS 등 여러 계층이 이전 IP 주소를 캐시에 보관할 수 있다. 어느 한 계층이라도 오래된 주소를 반환하면 애플리케이션은 이미 사용할 수 없는 이전 데이터베이스로 연결을 시도한다.

커넥션 풀도 중요한 변수다. HikariCP와 같은 커넥션 풀에는 장애 전 생성된 연결이 남아 있을 수 있다. 애플리케이션이 해당 연결을 빌려 쿼리를 실행하는 시점에야 연결 단절을 발견하면 Connection reset, Communications link failure, I/O error, The connection is closed 같은 오류가 반복된다.

따라서 RDS의 장애 조치 완료 시간과 서비스의 실제 정상화 시간은 서로 다를 수 있다. 데이터베이스 역할 전환은 끝났지만 DNS 갱신, 기존 세션 폐기, 신규 세션 생성이 완료되지 않았다면 애플리케이션에서는 장애가 계속되는 것처럼 보인다.

DNS 캐시 계층별 진단 방법

가장 먼저 확인할 항목은 애플리케이션 서버에서 RDS 엔드포인트가 어떤 IP로 해석되는지다. 장애 조치 전후에 다음과 같은 명령을 실행해 결과를 기록하면 DNS 갱신 여부를 비교할 수 있다.

dig rds-endpoint.region.rds.amazonaws.com
nslookup rds-endpoint.region.rds.amazonaws.com
getent hosts rds-endpoint.region.rds.amazonaws.com

한 번의 조회 결과만으로 판단해서는 안 된다. 여러 애플리케이션 서버와 컨테이너에서 반복적으로 확인해야 한다. 특정 서버만 이전 IP를 반환한다면 RDS보다 해당 서버의 로컬 DNS 캐시나 JVM 캐시를 의심하는 것이 합리적이다.

Java 애플리케이션은 JVM 내부에서 DNS 조회 결과를 캐시한다. AWS는 RDS 같은 리소스의 IP 변경에 대응할 수 있도록 JVM DNS TTL을 60초 이하로 구성할 것을 권장한다. 실행 중인 애플리케이션에서는 다음 값을 확인할 수 있다.

String ttl = java.security.Security.getProperty("networkaddress.cache.ttl");
String negativeTtl =
    java.security.Security.getProperty("networkaddress.cache.negative.ttl");

networkaddress.cache.ttl-1이면 DNS 정보를 무기한 보관할 가능성이 있다. 다만 실제 기본값과 적용 방식은 JDK 버전과 실행 환경에 따라 달라질 수 있으므로 설정 파일만 확인하지 말고 실행 중인 JVM에서 값을 확인해야 한다. 존재하지 않는 주소에 대한 실패 결과를 저장하는 음수 캐시 TTL도 지나치게 길지 않은지 점검할 필요가 있다.

Kubernetes 환경이라면 Pod 내부의 /etc/resolv.conf, CoreDNS 로그, DNS 응답 시간과 캐시 설정도 함께 확인해야 한다. 같은 서비스의 Pod 중 일부만 정상화되지 않는다면 Pod별 JVM 캐시 또는 각 Pod가 보유한 커넥션 풀의 상태 차이가 원인일 가능성이 높다.

커넥션 풀에서 확인해야 할 핵심 설정

DNS가 새로운 IP를 정상적으로 반환해도 커넥션 풀에 장애 전 연결이 남아 있으면 오류가 지속될 수 있다. HikariCP를 사용한다면 maximumPoolSize, connectionTimeout, validationTimeout, idleTimeout, maxLifetime, keepaliveTime을 우선 확인해야 한다.

connectionTimeout은 풀에서 연결을 얻기 위해 기다리는 최대 시간이지 장애 복구 시간을 직접 줄여주는 설정은 아니다. maxLifetime 역시 연결의 최대 생존 시간을 제한할 뿐, 장애가 발생한 순간 모든 기존 연결을 즉시 제거하는 기능으로 이해해서는 안 된다. keepaliveTime은 유휴 연결의 유효성을 주기적으로 확인하는 데 도움이 되지만 너무 짧게 설정하면 불필요한 검증 트래픽을 만들 수 있다.

중요한 것은 JDBC 드라이버가 끊어진 연결을 정확히 감지하고, 풀이 해당 연결을 제거한 뒤 새로운 IP로 재연결하도록 만드는 것이다. JDBC URL에는 RDS 엔드포인트를 사용해야 하며 장애 조치가 가능한 환경에서 특정 DB 인스턴스의 IP를 직접 입력하면 안 된다.

커넥션 풀 크기도 점검해야 한다. 장애가 해제되는 순간 여러 Pod가 동시에 최대 풀 크기만큼 연결을 생성하면 새로운 기본 인스턴스에 연결 폭증이 발생한다. 예를 들어 Pod 30개에 maximumPoolSize가 30이면 이론적으로 900개의 연결이 짧은 시간에 몰릴 수 있다. 이때 데이터베이스가 정상이어도 인증, TLS 연결, 세션 초기화 부하 때문에 복구가 늦어질 수 있다.

재시도는 고정된 짧은 간격보다 지수 백오프와 무작위 지연을 적용하는 편이 안전하다. 모든 서버가 같은 순간에 재연결하지 않도록 해야 장애 직후의 연결 폭주를 줄일 수 있다.

운영 환경에서 적용한 트러블슈팅 순서와 해결책

개인적으로 유사한 장애를 분석할 때 가장 효과적이었던 방법은 데이터베이스, DNS, 애플리케이션을 분리해서 시간순으로 확인하는 것이었다. 처음에는 RDS 장애 조치가 늦게 끝났다고 판단하기 쉬웠지만 RDS 이벤트에서는 이미 새로운 기본 인스턴스 전환이 완료된 상태였다.

애플리케이션 서버별 로그를 비교해 보니 일부 서버는 약 1분 안에 새로운 연결을 만들었고, 다른 서버는 재시작하기 전까지 연결 오류가 이어졌다. 정상 서버와 비정상 서버의 차이는 실행 중인 JVM의 DNS 캐시 상태와 기존 커넥션 풀에 남아 있던 연결이었다. 애플리케이션 재시작으로 문제가 해결된 이유도 재시작 과정에서 JVM DNS 캐시와 커넥션 풀이 동시에 초기화됐기 때문이다.

다만 재시작은 긴급 복구 수단일 뿐 근본적인 해결책은 아니다. 운영 환경에서는 다음 순서로 점검하는 것이 효율적이다.

첫째, RDS 이벤트에서 장애 조치 시작 시각과 완료 시각을 확인한다. 둘째, 각 서버와 Pod에서 엔드포인트의 IP 해석 결과를 비교한다. 셋째, JVM DNS TTL과 음수 캐시 TTL을 실제 런타임 기준으로 확인한다. 넷째, 커넥션 풀의 활성·유휴·대기 연결 수와 연결 생성 실패 로그를 함께 분석한다. 다섯째, 신규 연결이 현재 기본 인스턴스로 들어오는지 데이터베이스 세션 정보로 검증한다.

장애 조치 훈련도 반드시 필요하다. 트래픽이 적은 시간에 수동 재부팅과 장애 조치를 수행하면서 연결 오류 시작 시각, RDS 전환 완료 시각, 첫 번째 신규 연결 성공 시각, 전체 서비스 정상화 시각을 각각 기록하면 실제 복구 목표를 수치화할 수 있다.

DNS 캐시 문제를 애플리케이션에서 완전히 통제하기 어렵거나 서버리스·컨테이너 환경에서 연결 수가 급변한다면 RDS Proxy도 검토할 수 있다. RDS Proxy는 데이터베이스 역할 변경을 애플리케이션으로부터 추상화하고 DNS 캐시와 연결 폭증의 영향을 줄이는 데 도움이 된다. 다만 추가 비용, 일부 세션 상태에 따른 연결 고정, 애플리케이션 특성 등을 사전에 검토해야 한다.

마치며

AWS RDS 장애 조치 후 연결이 늦게 복구되는 문제는 단순히 “DNS 전파가 느리다”는 말로 끝낼 수 없다. RDS 엔드포인트 변경, JVM과 운영체제의 DNS 캐시, Kubernetes DNS 계층, JDBC 드라이버, 커넥션 풀의 기존 세션, 애플리케이션 재시도 정책을 하나의 흐름으로 분석해야 정확한 원인을 찾을 수 있다.

특히 애플리케이션 재시작 후 즉시 정상화됐다면 데이터베이스 장애가 계속된 것이 아니라 DNS 캐시 또는 커넥션 풀이 초기화되면서 해결됐을 가능성이 크다. 이 경우 재시작 절차만 운영 매뉴얼에 추가하기보다 JVM DNS TTL, 연결 유효성 검사, 풀 크기, 재시도 백오프를 개선해야 같은 장애의 재발을 막을 수 있다.

고가용성 구성의 목적은 장애 조치 기능을 켜는 데서 끝나지 않는다. 장애 이후 애플리케이션이 새로운 데이터베이스를 얼마나 빠르고 안정적으로 찾아가는지까지 검증해야 실질적인 고가용성을 확보할 수 있다.

PRODB

함께 보면 좋은 글

댓글 0

첫 댓글을 남겨보세요.

error: Content is protected !!

광고 차단 알림

광고 클릭 제한을 초과하여 광고가 차단되었습니다.

단시간에 반복적인 광고 클릭은 시스템에 의해 감지되며, IP가 수집되어 사이트 관리자가 확인 가능합니다.