← 개발일지

모바일 배포 후 간헐적으로 반영이 안 될 때 — Java 멀티 인스턴스 환경 트러블슈팅 가이드


모바일 배포 후 간헐적으로 반영이 안 될 때 — Java 멀티 인스턴스 환경 트러블슈팅 가이드

Java 멀티 인스턴스 환경에서 배포 후 변경사항이 새로고침할 때마다 되었다 안 되었다 하는 경험, 해보신 적 있으신가요? JAR 파일도 정상 배포되었고, 코드에 조건 분기도 없는데 간헐적으로 반영이 안 되는 상황. 이 글에서는 실제로 겪었던 이 문제의 원인과 해결 과정을 단계별로 정리했습니다.


목차

  1. 문제 상황
  2. 원인을 추측해본 것들
  3. 실제 원인
  4. 해결 과정 — 단계별 가이드
  5. 알아두면 좋은 리눅스 명령어 정리
  6. 재발 방지 체크리스트
  7. 마무리

1. 문제 상황

환경

  • Spring Boot 기반 Java 웹 애플리케이션
  • PC용과 모바일용이 별도 JAR로 분리 운영 (fo-pc, fo-mobile)
  • 여러 대의 서버(멀티 인스턴스)에 동일한 앱을 배포하고, 앞단에 로드밸런서가 요청을 분배하는 구조

증상

모바일 웹에 새로운 UI 요소를 추가하여 배포했습니다. 그런데 배포 후 확인해보니 이상한 현상이 발생했습니다.

  • 새로고침하면 대부분은 정상 반영되어 보임
  • 그런데 가끔씩 반영이 안 된 이전 화면이 나타남
  • 대략 5~6번 중 1번꼴로 이전 버전이 노출
  • 특정 조건이나 코드 분기와는 무관

JAR 파일을 각 서버에서 직접 확인해봐도 모든 서버에 신규 JAR이 정상적으로 배포되어 있었습니다.


2. 원인을 추측해본 것들

가설 1: 특정 인스턴스에 배포가 안 된 것 아닐까?

→ 각 서버의 JAR 파일을 직접 확인했지만, 모두 최신 버전이 존재했습니다. 배포 자체는 문제가 아니었습니다.

가설 2: PC/모바일 분기 로직 문제가 아닐까?

이 프로젝트는 모바일 접속 시 User-Agent를 판별하여 모바일 URL로 리다이렉트하는 구조였습니다. 변경사항을 모바일에만 적용했으므로, 혹시 PC 버전 페이지가 내려오는 건 아닌지 의심했습니다.

→ 하지만 URL을 확인해봐도 모바일 URL이 정상적으로 나오고 있었습니다.

가설 3: 재기동이 제대로 안 된 인스턴스가 있는 것 아닐까?

정답은 이것이었습니다.


3. 실제 원인

JAR은 배포됐지만, 재기동이 실패한 인스턴스가 존재

원인을 정리하면 이렇습니다.

JAR 파일 신규 배포 (정상)
        ↓
수동 재시작 시도
        ↓
재시작 명령이 실제로는 동작하지 않음 ← ★ 여기가 문제
        ↓
구버전 JAR을 물고 있는 프로세스가 그대로 살아있음
        ↓
로드밸런서가 이 인스턴스로 요청을 보내면 이전 버전이 응답
        ↓
"되었다 안 되었다" 하는 간헐적 현상 발생

재시작이 실패한 이유

이 케이스에서는 권한 문제가 원인이었습니다.

서비스 제어 스크립트(ctl.sh 같은)를 실행할 때, 해당 서비스를 기동한 계정과 동일한 권한이 필요합니다. 권한이 없는 계정으로 stop/start를 실행하면, 에러 메시지 없이 조용히 실패하는 경우가 있습니다.

특히 이 경우에는 한 인스턴스의 프로세스가 root 권한으로 실행되어 있었습니다. 일반 계정으로는 root 프로세스를 종료할 수 없으므로 재시작이 불가능했던 것입니다.

왜 "간헐적"이었는가

멀티 인스턴스 환경에서 로드밸런서는 라운드로빈 등의 방식으로 요청을 분배합니다. 5대의 인스턴스 중 4대는 정상 재기동되어 신버전이 떠 있었고, 1대만 구버전이 남아 있었기 때문에, 새로고침할 때마다 결과가 달랐던 것입니다.


4. 해결 과정 — 단계별 가이드

같은 문제를 겪고 있다면 아래 순서대로 확인해보세요.

Step 1. 각 인스턴스의 프로세스 시작 시간 확인

서버에 SSH로 접속한 뒤, 아래 명령어로 해당 애플리케이션의 프로세스 시작 시간을 확인합니다.

ps -eo pid,lstart,cmd | grep [앱이름]

실행 결과 예시는 다음과 같습니다.

236524 Mon Apr 13 13:53:14 2026 java -Xms2048m ... -jar /path/to/app-fo-mobile-1.0.jar

여기서 Mon Apr 13 13:53:14 2026 부분이 프로세스가 시작된 정확한 날짜와 시간입니다.

| 출력 항목 | 의미 | |---|---| | 236524 | PID (프로세스 고유 번호) | | Mon Apr 13 13:53:14 2026 | 프로세스 시작 일시 | | java ... -jar ... | 실행된 명령어 |

Step 2. JAR 파일의 수정 날짜 확인

stat /path/to/app-fo-mobile-1.0.jar

출력에서 Modify 항목이 JAR 파일이 마지막으로 변경(배포)된 시점입니다.

Modify: 2026-04-13 10:30:22.000000000 +0900

Step 3. 두 날짜를 비교

| 비교 결과 | 의미 | |---|---| | 프로세스 시작 > JAR 수정 | 정상. 신버전 JAR로 기동된 상태 | | 프로세스 시작 < JAR 수정 | 비정상. 구버전이 실행 중 → 재기동 필요 |

모든 인스턴스(서버)에 SSH 접속하여 동일하게 확인합니다. 프로세스 시작 시간이 JAR 배포 시점보다 이전인 인스턴스가 있다면, 그 인스턴스가 문제의 원인입니다.

Step 4. 문제 인스턴스 재기동

재기동 전 반드시 확인할 것:

whoami

현재 로그인한 계정이 서비스 기동 권한이 있는 계정인지 반드시 확인하세요. 이것을 빠뜨리면 이번과 같은 문제가 반복됩니다.

권한이 확인되었다면, 프로젝트에서 사용하는 제어 스크립트로 재기동합니다.

# 예시 (프로젝트마다 다름)
cd /path/to/app/bin
./ctl.sh [서비스명] stop
./ctl.sh [서비스명] start

Step 5. 재기동 확인

ps -eo pid,lstart,cmd | grep [앱이름]

프로세스 시작 시간이 방금 전 시간으로 갱신되었는지 확인합니다.

Step 6. 실제 서비스 확인

브라우저에서 해당 페이지에 접속하여 변경사항이 정상 반영되는지 확인합니다. 여러 번 새로고침하여 모든 인스턴스에서 동일하게 나오는지 검증합니다.


5. 알아두면 좋은 리눅스 명령어 정리

이 트러블슈팅 과정에서 사용한 명령어들을 정리했습니다. 서버 운영에 익숙하지 않은 분들도 이해할 수 있도록 하나씩 설명합니다.

프로세스 확인

# 모든 프로세스 중 특정 앱만 필터링하여 시작 시간 확인
ps -eo pid,lstart,cmd | grep [앱이름]

| 구성 요소 | 설명 | |---|---| | ps | Process Status. 현재 실행 중인 프로세스 목록을 보여주는 명령어 | | -e | 모든 프로세스 대상 (내 것뿐 아니라 서버 전체) | | -o pid,lstart,cmd | 출력할 항목을 직접 지정. pid=프로세스 번호, lstart=시작 일시, cmd=실행 명령어 | | \| (파이프) | 앞 명령어의 출력을 뒤 명령어의 입력으로 전달 | | grep [앱이름] | 전달받은 텍스트에서 특정 문자열이 포함된 줄만 필터링 |

💡 ps -ef와의 차이점: ps -ef는 기본 포맷으로 출력하는데, 시작 날짜가 생략될 수 있습니다. 배포 문제를 확인할 때는 정확한 날짜가 중요하므로 반드시 ps -eo pid,lstart,cmd를 사용하세요.

파일 정보 확인

# JAR 파일의 수정 시간, 크기, 권한 등 확인
stat /path/to/application.jar

| 출력 항목 | 의미 | 용도 | |---|---|---| | Size | 파일 크기 (바이트) | 배포된 파일이 정상인지 확인 | | Modify | 내용이 마지막으로 변경된 시간 | JAR이 언제 배포되었는지 확인 | | Change | 속성(권한 등)이 마지막으로 변경된 시간 | 권한 변경 이력 확인 |

# 디렉토리 내 파일을 수정일 기준 정렬
ls -lt /path/to/app/

가장 최근에 수정된 파일이 맨 위에 나옵니다. 배포된 JAR이 최신인지 한눈에 확인할 수 있습니다.

파일 내용 확인

# 스크립트 내용 확인
cat ctl.sh

cat은 파일 내용을 터미널에 그대로 출력합니다. 제어 스크립트에 어떤 인자를 넣어야 하는지 확인할 때 사용합니다.

💡 파일이 길 때: less ctl.sh를 사용하면 페이지 단위로 스크롤할 수 있습니다. 방향키로 이동하고 q로 나갑니다.

계정 확인

# 현재 로그인된 계정 확인
whoami

재기동 전에 반드시 실행하여 권한 있는 계정인지 확인하는 습관을 들이세요. 이 한 줄이 불필요한 장애를 예방해줍니다.

로그 모니터링

# 로그 파일을 실시간으로 모니터링
tail -f /path/to/logs/application.log

# 마지막 200줄부터 실시간 모니터링
tail -200f /path/to/logs/application.log

| 옵션 | 설명 | |---|---| | -f | follow. 파일에 새 내용이 추가되면 실시간으로 계속 출력 | | -200 | 마지막 200줄부터 시작 |

재기동 후 에러가 발생하는지 확인하거나, 요청이 정상적으로 들어오는지 모니터링할 때 사용합니다. 종료는 Ctrl + C입니다.

프로세스 수동 종료 (비상시에만)

# 정상 종료 (프로세스가 마무리 작업 후 종료)
kill -15 [PID]

# 강제 종료 (응답 없을 때 최후 수단)
kill -9 [PID]

| 신호 | 이름 | 설명 | |---|---|---| | -15 | SIGTERM | 프로세스에게 종료를 요청. 정리 작업 후 종료됨 | | -9 | SIGKILL | 즉시 강제 종료. -15로 안 죽을 때만 사용 |

⚠️ 주의: 운영 환경에서는 kill보다 프로젝트의 제어 스크립트(ctl.sh 등)를 사용하는 것이 안전합니다. 스크립트는 포트 해제, 로그 처리 등 후처리까지 포함하고 있기 때문입니다.


6. 재발 방지 체크리스트

배포 후 아래 항목을 습관적으로 확인하면 같은 문제를 예방할 수 있습니다.

  • [ ] whoami로 현재 계정이 서비스 기동 권한이 있는 계정인지 확인했는가
  • [ ] stat으로 JAR 파일의 Modify 날짜가 오늘 배포 시점인지 확인했는가
  • [ ] ps -eo pid,lstart,cmd로 프로세스 시작 시간이 JAR 배포 이후인지 확인했는가
  • [ ] 모든 인스턴스에서 위 항목을 확인했는가 (한 대만 확인하고 넘어가지 않았는가)
  • [ ] 재기동 후 브라우저에서 여러 번 새로고침하여 모든 인스턴스 응답을 검증했는가

7. 마무리

이번 문제의 핵심을 한 문장으로 요약하면 이렇습니다.

"JAR 파일이 배포된 것"과 "배포된 JAR이 실행 중인 것"은 다르다.

파일이 서버에 존재하는 것만으로는 배포가 완료된 것이 아닙니다. 해당 파일을 물고 있는 프로세스가 재기동되어야 비로소 반영되는 것이고, 멀티 인스턴스 환경에서는 모든 인스턴스에서 이를 확인해야 합니다.

그리고 재기동이 실패하는 가장 흔한 원인 중 하나가 권한 문제입니다. 에러 없이 조용히 실패하는 경우가 있으므로, 재기동 전 whoami로 계정을 확인하고, 재기동 후 ps로 프로세스 시작 시간이 갱신되었는지 반드시 검증하는 습관을 들이는 것을 추천합니다.

이 글이 비슷한 문제를 겪고 계신 분들에게 도움이 되었으면 좋겠습니다.


Java 멀티 인스턴스 배포 문제, Spring Boot 배포 반영 안됨, 새로고침마다 다른 결과, 로드밸런서 인스턴스 배포 확인, 리눅스 프로세스 시작 시간 확인 방법