본문 바로가기
삵
Java

자바 앱이 장애조치 후 옛 IP로 붙을 때

강강철지그·2026년 10월 21일·조회 0

RDS 페일오버나 Route 53 장애조치를 걸어두고 나면 "DNS만 바뀌면 자동으로 새 서버로 붙겠지"라고 생각하기 쉽다. 그런데 제가 현장에서 자주 보는 장면은, 다른 언어로 짠 서비스는 수 초 만에 새 엔드포인트로 넘어가는데 유독 자바로 띄운 WAS 하나만 몇 분이 지나도 죽은 옛 IP를 물고 늘어지는 상황이다. nslookup으로 호스트를 찍어보면 이미 새 주소가 돌아오는데도 그렇다. 이 글은 그 범인인 JVM DNS 캐시를 어떻게 읽고 맞추는지를 정리한 것이다.

결론부터 말하자면, JVM은 호스트 이름을 한 번 풀면 그 결과(IP)를 프로세스 안에 일정 시간 캐싱한다. 이 TTL이 DNS 레코드의 TTL이나 장애조치 주기보다 길면, 네임서버가 새 주소를 내놓아도 애플리케이션은 옛 IP로 계속 접속을 시도한다. 양수 캐시(networkaddress.cache.ttl)를 짧게 잡고, 커넥션 풀의 maxLifetime으로 기존 연결을 주기적으로 교체하면 장애조치 후 자동 재접속이 된다.

1. JVM DNS 캐시가 무엇이고 왜 존재하나

java.net.InetAddress는 호스트 이름을 IP로 바꾼 결과를 프로세스 안에 캐싱한다. 매번 리졸버를 거치면 느리고, 과거에는 DNS 스푸핑을 막으려는 보안 목적도 있었다. 그래서 자바는 운영체제나 DNS 레코드의 TTL과 무관하게 자체 TTL을 둔다. 이게 이번 문제의 핵심이다. 운영체제 리졸버는 이미 새 IP를 알고 있어도, JVM 내부 캐시가 만료되지 않으면 옛 값을 돌려준다.

캐시는 두 종류로 나뉜다. 성공한 조회(양수 캐시)는 networkaddress.cache.ttl이, 실패한 조회(음수 캐시)는 networkaddress.cache.negative.ttl이 각각 보관 시간을 초 단위로 정한다. 둘 다 자바 보안 속성(security property)이다.

  • networkaddress.cache.ttl: 성공한 이름 조회를 몇 초간 캐싱할지. -1은 영구 캐시, 0은 캐싱 안 함.
  • networkaddress.cache.negative.ttl: 실패한 조회를 몇 초간 캐싱할지. 기본값 10, 0은 캐싱 안 함, -1은 영구.

2. 기본값이 환경마다 다르다는 함정

여기서 한 번 걸린다. 양수 캐시의 기본값이 고정이 아니다. 공식 javadoc은 "보안 관리자(SecurityManager)가 설치돼 있으면 성공한 조회를 영구 캐싱한다"고 적는다. 보안 관리자가 없으면 "구현에 따라 다른 유한한 기간"이라고만 되어 있다. 핫스팟 JVM은 이 경우 대체로 30초를 쓴다.

문제는 옛 코드다. 아주 오래된 환경이나 보안 관리자를 켠 배치에서는 양수 캐시가 사실상 영구가 된다. 이러면 장애조치가 일어나도 애플리케이션을 재기동하기 전까지 절대 새 IP로 붙지 않는다. "우리 앱은 재시작해야만 DB가 바뀐다"는 경험담의 뿌리가 대개 여기다.

그러니 기본값에 기대지 말고 명시적으로 지정하는 쪽을 권한다. 값을 직접 박아두면 JVM이나 배포판이 바뀌어도 동작이 흔들리지 않는다.

3. 현재 캐시 설정 확인하기

먼저 자바 버전과 보안 속성 파일 위치를 확인한다.

$ java -version
openjdk version "17.0.11" 2024-04-16
OpenJDK Runtime Environment Temurin-17.0.11+9 (build 17.0.11+9)
OpenJDK 64-Bit Server VM Temurin-17.0.11+9 (build 17.0.11+9, mixed mode, sharing)

$ java -XshowSettings:properties -version 2>&1 | grep java.home
    java.home = /usr/lib/jvm/temurin-17-jdk-amd64

보안 속성 파일에서 두 값을 확인한다. 대개 주석 처리돼 있어 기본값으로 동작한다.

$ grep -n "networkaddress.cache" /usr/lib/jvm/temurin-17-jdk-amd64/conf/security/java.security
79:#networkaddress.cache.ttl=-1
89:networkaddress.cache.negative.ttl=10

실제 캐시가 얼마나 끈질긴지는 jshell로 눈으로 확인하는 편이 빠르다. 같은 호스트를 반복 조회해 IP가 고정되는지 본다.

$ jshell -R-Dsun.net.inetaddr.ttl=30
jshell> java.net.InetAddress.getByName("db.internal.example.com")
$1 ==> db.internal.example.com/10.0.1.20

이 상태에서 DNS 레코드를 새 IP로 바꾼 뒤 30초 전과 후에 같은 명령을 다시 실행해 보면, TTL이 지나기 전에는 10.0.1.20이 그대로 나오고 지난 뒤에야 새 주소로 바뀐다. 이 지연이 바로 장애조치가 늦게 반영되는 구간이다.

4. TTL을 장애조치 주기에 맞춰 설정하기

설정 방법은 세 가지다. 값의 일관성과 운영 편의를 보면 보안 속성 파일을 직접 고치는 쪽이 가장 확실하다.

java.security 파일 수정

JVM 전역에 적용된다. RDS나 Route 53 장애조치를 쓰는 환경이라면 양수 캐시를 짧게 둔다.

networkaddress.cache.ttl=30
networkaddress.cache.negative.ttl=0

음수 캐시를 0으로 두는 이유가 있다. 장애조치 순간에는 옛 IP가 잠깐 응답을 끊어 조회가 실패할 수 있는데, 이때 실패 결과를 10초씩 캐싱하면 복구가 그만큼 늦어진다. 0이면 다음 조회에서 바로 다시 물어본다.

기동 옵션으로 지정

보안 파일을 못 건드리는 상황이면 별도 속성 파일을 -Djava.security.properties로 덧씌운다.

$ cat /app/conf/dns.security
networkaddress.cache.ttl=30
networkaddress.cache.negative.ttl=0

$ java -Djava.security.properties=/app/conf/dns.security -jar app.jar

참고로 -Dsun.net.inetaddr.ttl=30 같은 시스템 속성도 동작한다. 다만 이건 내부 속성이라 명시적인 보안 속성이 우선한다. 운영 표준으로는 networkaddress.cache.ttl을 쓰는 쪽을 권한다.

코드에서 지정

프레임워크 초기화 지점에서 직접 걸 수도 있다. 단 InetAddress가 처음 호출되기 전에 설정해야 반영된다.

java.security.Security.setProperty("networkaddress.cache.ttl", "30");
java.security.Security.setProperty("networkaddress.cache.negative.ttl", "0");

5. TTL만 줄여도 안 바뀌는 이유 - 이미 열린 커넥션

TTL을 30초로 줄였는데도 DB가 안 넘어간다는 문의가 여기서 또 나온다. 이유는 간단하다. DNS 캐시는 "이름을 새로 풀 때"만 관여한다. 커넥션 풀이 장애조치 전에 맺어둔 TCP 연결은 이미 소켓이 옛 IP로 고정돼 있다. 풀이 그 연결을 재사용하는 한 DNS를 아무리 새로 풀어도 소용이 없다.

그래서 커넥션 풀의 최대 수명을 같이 손봐야 한다. HikariCP의 maxLifetime은 풀 안의 연결이 살아 있을 수 있는 최대 시간이다. 이 시간이 지나면 (사용 중이 아닌) 연결을 폐기하고 새로 맺는데, 새로 맺을 때 DNS를 다시 풀면서 그제야 새 IP로 붙는다. 기본값은 1800000ms, 30분이다.

공식 문서는 이 값을 "데이터베이스나 인프라가 강제하는 연결 시간 제한보다 몇 초 짧게" 두라고 권한다. 장애조치 복구 시간을 당기려면 더 공격적으로 줄인다. 최소 허용값은 30000ms(30초)다.

# application.yml (Spring Boot + HikariCP)
spring:
  datasource:
    hikari:
      max-lifetime: 180000      # 3분. 이 시간마다 유휴 연결이 교체된다
      keepalive-time: 60000     # 유휴 연결 생존 확인
      connection-timeout: 5000  # 새 연결 획득 대기 상한
      validation-timeout: 3000

maxLifetime을 3분으로 두면 장애조치 후 길어도 3분 안에 모든 유휴 연결이 새 IP 기반으로 교체된다. DNS TTL 30초와 합치면 "이름은 30초 안에, 연결은 3분 안에" 갱신되는 셈이다. 둘 중 하나만 줄이면 반쪽짜리다.

6. 장애조치 순간에 실패한 연결은 어떻게 되나

유휴 연결 교체만으로는 장애조치 바로 그 순간 쓰고 있던 연결을 못 구한다. 옛 IP가 응답을 끊으면 그 위에서 돌던 쿼리는 예외로 끝난다.

org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend.
  Caused by: java.net.SocketException: Connection reset

HikariCP는 연결을 빌려줄 때 유효성을 검사하므로, 죽은 연결은 풀에서 걸러지고 새 연결을 맺으려 시도한다. 이때 DNS가 새 IP를 돌려주면 자동 복구된다. 다만 진행 중이던 트랜잭션 자체는 실패하므로, 애플리케이션 레벨의 재시도(예: Spring Retry)나 멱등한 작업 설계가 같이 필요하다. 인프라 설정과 코드가 함께 맞물려야 무중단에 가까워진다.

7. 점검 체크리스트

  • 장애조치를 쓰는 환경이면 networkaddress.cache.ttl을 명시적으로(예: 30) 지정했는가. 기본값과 보안 관리자 유무에 의존하지 않는가.
  • networkaddress.cache.negative.ttl을 0이나 짧게 두어 장애조치 순간의 조회 실패가 오래 캐싱되지 않게 했는가.
  • 커넥션 풀 maxLifetime이 DB/인프라 타임아웃보다 짧고, 장애조치 복구 목표 시간 안에 연결이 교체되도록 잡혔는가.
  • DNS 레코드 자체의 TTL이 충분히 짧은가. 레코드 TTL이 300초면 운영체제 리졸버 단계에서 이미 5분이 묶인다.
  • 재시도와 멱등성으로 장애조치 순간의 실패 트랜잭션을 처리하는가.

자주 묻는 질문

networkaddress.cache.ttl의 기본값은 얼마인가?

공식 javadoc은 고정값을 명시하지 않는다. 보안 관리자(SecurityManager)가 설치돼 있으면 성공한 조회를 영구 캐싱하고, 없으면 구현에 따라 다른 유한한 기간을 쓴다. 핫스팟 JVM은 보안 관리자가 없을 때 대체로 30초를 적용한다. 환경에 따라 달라지므로 장애조치를 쓴다면 값을 직접 지정하는 편을 권한다.

TTL을 줄였는데도 장애조치 후 DB가 안 바뀐다.

DNS 캐시는 이름을 새로 풀 때만 관여한다. 커넥션 풀이 장애조치 전에 맺어둔 TCP 연결은 옛 IP로 고정돼 있어 재사용되는 한 바뀌지 않는다. HikariCP의 maxLifetime을 짧게 잡아 유휴 연결이 주기적으로 교체되고 새로 맺을 때 DNS를 다시 풀도록 해야 한다.

maxLifetime은 얼마로 두어야 하나?

HikariCP 기본값은 1800000ms(30분)이고 최소 허용값은 30000ms(30초)다. 공식 문서는 데이터베이스나 인프라가 강제하는 연결 시간 제한보다 몇 초 짧게 두라고 권한다. 장애조치 복구 시간을 당기려면 3분 전후로 줄이고, DNS TTL과 함께 맞춘다.

negative.ttl은 왜 0으로 두나?

장애조치 순간에는 옛 IP가 응답을 끊어 이름 조회가 잠깐 실패할 수 있다. 음수 캐시 기본값 10초를 그대로 두면 이 실패 결과가 캐싱돼 복구가 늦어진다. 0으로 두면 다음 조회에서 바로 다시 리졸버에 물어본다.

sun.net.inetaddr.ttl와 networkaddress.cache.ttl 중 무엇을 쓰나?

둘 다 동작하지만 sun.net.inetaddr.ttl은 내부 시스템 속성이다. 명시적으로 지정한 보안 속성 networkaddress.cache.ttl이 우선한다. 운영 표준으로는 java.security 파일이나 java.security.properties로 networkaddress.cache.ttl을 쓰는 쪽을 권한다.

관련 글

댓글 0

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.