11월 8일자로 진행된 부산대-부경대 연합 해킹캠프에서 'Hack-Playground' 서비스 내의 CTF 대회를 시범적으로 도입했다.

서비스 자체는 KOREN망 안에서 호화롭게 실험하고 성능 걱정없이 트래픽 때려박던 공모전 기간이 끝나면서, 초라한 운영 서버로 겨우 무료로 연명하고 있었다.
1. 학술 행사에서의 서비스 실사용
부산대와 부경대 정보보안 동아리끼리 문제를 제작했고, 해운대에 위치한 동남정보보호클러스터의 훈련장을 지원받아서 오프라인으로 행사를 진행했다.
12시부터 오프라인으로 대회장에 모여서 회원가입 및 소속을 등록하는 참가 수속을 진행하고, 13시부터 CTF 대회를 진행했다.
최대 5명까지 팀을 생성 혹은 참가해서 경쟁하는 구조인데, 무려 8시간 넘게 진행되기에 색다른 트래픽 경험이 될거라 생각했다.


2. Metaspace OOM 발생으로 인한 서비스 중단
대회 운영이 잘 흘러가는 것을 보고 점심 식사하러 갔다가, 서버가 중단되었다는 연락을 받았다.
Grafana 대시보드도 열어서 모니터링 환경을 맞춰놨기에 확연하게 문제가 벌어졌음을 알 수 있었다.

애플리케이션에 뜨는 오류 로그는 java.lang.OutOfMemoryError: Metaspace였다.
2-1. 원인 분석
애초에 최대한 운영 비용을 절감하기 위해서 서비스를 AWS t2.micro 유형으로 장시간 돌려왔다. 그 작은 CPU 1core 메모리 1GB 에 서비스를 운영하기 위해 기를 쓰고 온몸비틀기를 해왔던 여파가 돌아왔던 셈이다.
JVM 튜닝을 통한 메모리 제한과 컨테이너 리소스 제한 등을 덕지덕지 우겨넣어서 반년째 죽여달라고 빌던 녀석이다.
오류 로그를 보자마자 이미 오래전부터 인지해둔 녀석임을 직감하고 바로 문제를 해결하고자 했다.
-XX:MaxMetaspaceSize=128m
JVM Metaspace 영역의 최대 크기를 제한해두니 OOM 터져버린 것이다. 특히나 Metaspace는 Heap 메모리가 아니라 Native쪽이라 제한해버린 컨테이너 전체 메모리에 포함된다.

안전불감증같은게 아니라 진짜 고작 이정도 트래픽으로 OOM을 경험하게 될 줄 몰랐는데, 근데 다시 생각해보니 스프링 애플리케이션을 돌리면서 내가 너무 잔인했다.
시작할때부터 클래스 metadata만으로 126MB 쓰고 시작하는 녀석한테 128MB만 줘버린거다.
gc-logs:
GC(7213) Pause Full (Metadata GC Threshold)
GC(7213) Metaspace: 129605K(131072K)->129605K(131072K) NonClass: 113691K(114304K)->113691K(114304K) Class: 15913K(16768K)->15913K(16768K)
GC(7213) Pause Full (Metadata GC Threshold) 81M->81M(256M) 286.945ms
GC(7214) Pause Full (Metadata GC Clear Soft References)
GC(7214) Metaspace: 129605K(131072K)->129605K(131072K) NonClass: 113691K(114304K)->113691K(114304K) Class: 15913K(16768K)->15913K(16768K)
GC(7214) Pause Full (Metadata GC Clear Soft References) 81M->81M(256M) 286.973ms
Metaspace (data) allocation failed for size 23
아 G1 GC는 로그가 이벤트 단위라 너무 많고, ZGC랑 다르게 결과 분석이나 요약도 없어서 너무 분석하기 불편하다.
아래와 같이 필요한 키워드로 정제해서 새로운 summary.log를 만들어야 했다.
grep -E "Pause|Metaspace" g1-gc.log.4 | grep -v "\[debug\]" > gc_summary.log
뭐 정제해서 봐도 내용이 많긴한데,, 요약하자면 반복적으로 비슷한 내용이 나오고 결국 펑 터져버린 것이다.
[info][gc,metaspace] Pause Full (Metadata GC Threshold) 200ms
[info][gc,metaspace] Pause Full (Metadata GC Clear Soft References) 310ms
[info][gc,metaspace] Metaspace: 129605K used / 131072K Max
1. Metaspace 사용량이 임계치에 도달하면 Full GC (threshold)
2. Soft References 객체들을 정리하면서 Native 영역을 확보하려고 시도해보지만
3. 응 택도없어
트래픽 문제로 발생한 장애가 아니라, 시작부터 이미 Metaspace 한도를 거의 사용한 상태였고 클래스가 조금씩 로드되면서 대회 시작 1시간만에 꽉 차버린 것이다.
2-2. 문제 해결
이미 운영진을 포함해서 대회를 진행하던 수십명이 나만 기다리고 있는 상황이라 최대한 빨리 해결해야했다.
제한된 리소스 최대치를 없애기만 해도 해결되는 간단한 문제였다.
그런데 앞으로 남은 7시간 가까이동안 문제가 생기지 않도록 하기 위해 아예 서버 유형도 올려버렸던게 문제 해결에 더 지연된 원인이 되었다.
비용 문제로 당연히 Elastic IP같은 고정도 없었고 인스턴스 유형이 바뀌면서 백엔드 DNS 주소의 A레코드도 변경해줘야 했다.
여기까지 10분도 안걸렸는데, 이제 모든 참가자들이 변경된 DNS로 접속하는데 최장 30분이 걸렸다.
새로 변경할때야 짧게 TTL을 줄였지만 이미 예전 레코드를 TTL 30분으로 등록했던 탓에... 바꾸고 나서야 아차했다.
결과적으로 대회 자체는 서비스 중단으로 지연되었지만 메모리 확장 없이 OOM 해결해보려고 찾아보고 있는데, 클래스 로딩의 누수를 막거나 근본적으로 그 로딩 수를 줄이는 쪽으로 가닥 잡고 있다.
2-3. 후속 조치
장소를 지원받은 훈련장은 모든 참가자들을 수용할 네트워크가 부족했고, 일부 인원들(그리고 나도)은 핫스팟으로 접속해서 문제를 풀어야 했던 실정이었다.
그렇다보니 같은 팀안에서도 누구는 접속이 되고, 누구는 안되고... 저는 아직 접속이 안돼요... 저는 잘돼요...
최종적으로 모두 정상 동작하는데 40분 넘게 걸렸고, 그제서야 다시 바뀐 주소로 Prometheus를 연결하고 나니 1시간쯤 지났었다.

3. 설문 조사(참가자, 운영진)를 통한 사용자 경험 개선
퇴근하기 전에 각 팀들 돌아다니면서 플랫폼 사용간 불편함이 있는지 물어봤다.
- 쉘 접속시 TTY 문제로 인한 원격 접속 종료(netcat)
- 팀별 실시간 점수 현황표의 디자인이 별로다.
- 대회의 웹해킹 문제 접속하면 기존 설계와 다르게 <IP:Port> 형태로 뜬다. (플랫폼의 문제도 포트 이상하게 설정되는 이슈)
사실 대면한 상태에서 별로라고 말하기 어려울 수도 있다고 생각해서, 운영진들 통해 구글 폼 형태의 설문 조사를 부탁했었다.
크게 문제 풀이 소감 - 홈페이지 소감 - 대회 운영 소감으로 이뤄진 행사 전반에 대한 피드백이었는데, 많은 관심 주셔서 개선할 부분이 많이 보여서 다행이었다.
- JWT 만료시 토큰 재발급 기능으로 인해서 불편함을 느꼈다고 한다. 내가 안만든거 맞다
- 대회 운영시 문제를 삭제하는 경우, 추가적으로 확인받아서 실수로 삭제하는걸 막았으면 좋겠다.
- 문제 생성시 제목이 중복된 경우 이에 대한 안내 텍스트가 나오지 않아서 생성되지 않은 이유에 대해 파악하기 어려웠다.
- 실시간 점수 리더보드가 새로고침할때마다 초기화되어서 보기 어려웠다.
외에도 기존에 잘 동작하던 것도 기능 추가 과정에서 새롭게 오류가 발생하던 것도 많았는데, 참가자들 입장에서는 '의도된 응답'으로 생각해서 피드백받지 못한 부분도 대화해보면서 얻어갈 수 있었다.
'backend' 카테고리의 다른 글
| 토스가 겪은 Reactor Netty의 Memory Leak 이슈를 알아보자 (4) | 2025.08.18 |
|---|---|
| AOP를 이용한 @Transactional의 동작 원리 (feat. 트랜잭션의 범위, 전파) (1) | 2025.07.12 |
| JVM 튜닝 목적으로 Heap 덤프 분석은 처음 해봐요 (feat. Eclipse MAT) (0) | 2025.06.13 |
| What Color is Your Function? (feat. 함수의 색 전염성) (0) | 2025.03.12 |
| Java도 한다 경량 쓰레드 (Virtual Thread) (0) | 2025.02.26 |