Garbage Collection(사진 @Pixabay)


Garbage Collection.

C/C++은 객체를 선언하면, 명시적으로 Destroy 시켜줘야 한다.

그렇지 않으면 메모리를 할당한 채로 운영되다가, Out Of Memory 에러를 띄운다.


하지만 Java 는 명시적으로 Destroy 시켜주지 않아도 된다.

Java VM 이 자동으로 Destroy 시켜주기 때문이다.

물론 실시간으로 하는 건 아니고, 일정 시간을 두고 모아서 없앤다.

쓰레기 수집하는 것과 비슷해서 Garbage Collection 이라고 부른다.


문제는 이 기간 동안 Java VM 은 인입된 트래픽을 정지시켜 놓는다.

이게 순식간에 지나가면 문제가 없는데, 어떤 때는 5~6초를 넘어간다.

하드디스크 상의 SWAP Memory를 지우면서 I/O Bottle Neck 이 걸리는 거다.

SSD라면 상황이 조금 낫지만, 여전히 멈칫하는 현상은 발생한다.


서비스운영

한 때 Service 가 주기적으로 Hang이 걸렸던 적이 있었다.

매일 똑같은 시간대는 아니었지만, 트래픽이 몰리면 여지없이 비슷한 현상이 일어났다.


블로그 서비스라면 재접속하면 되지만, 과금 트랜잭션은 그렇지 않다.

정산불일치가 드물게 생기면서 민원이 종종 발생되었다.

이런 일이 반복되니 프로그램 불신이 생겼다. 일이 커졌다.


바쁜 일정 세워두고, 프로그램 검증을 하는 경우가 잦았다.

그렇다고 다른 일정을 미루지도 못했다. 업무 스트레스는 4배 늘어났다.

초반에 이 문제를 잡아야만, 개발 일정도 지키고 팀스트레스도 낮아질 수 있었다.

야근이다.


야근

단말 쪽에서 timeout이 우루루 함께 떨어지니 Server 쪽의 이상현상이 분명했다.

DB session을 모니터링 해봐도 Slow query 는 없다.


CPU usage 도 10% 내에서 안정적으로 사용되고 있다.

트래픽이 폭증하는 시간대에도 30% 수준을 넘지 않는다.


다만, 어떤 시점에 CPU 사용율이 60~70% 정도에 도달했는데 패턴이 일정치 않았다.

외부 병목이 없는 상황에서 CPU 사용율이 높아지는 건 분명 "시스템 소프트웨어" 문제다.

이리저리 좁혀가니 마지막에 Weblogic 이 남았다.


Weblogic을 모니터링하기 위해 Jupitor를 구동했다.

커다랗게 Full GC 가 일어나는 것이 눈에 들어왔다.


문제는 크게 잡아놓은 Java VM 의 Heap Memory 때문이었다.

핫타임을 수용할 수 있도록 한계량이 늘려놓았던 것이다.

큰 메모리를 비우려니 시간이 걸렸다.

Hard disk로 Memory SWAP 이 되는 순간에는 5분 이상 멈추기도 했다.


단기적 해결방향

단기적으로는 이런 방향을 모색해볼 수 있다.


- 메모리 Full 로 인한 SWAP Memory 사용을 원천적으로 막는다.

- Full GC 가 일어나기 전에 Minor GC 를 자주자주 하게 한다.


Full GC time이 길어지는 건 한번에 비우는 물리적 시간이 오래 걸리기 때문이다.

그러니 작게 자주 하도록 Heap size 를 적절히 줄여주었다.


그랬더니 timeout 발생빈도가 현저하게 낮아졌다. 하지만 새로운 문제가 생겼다.

Heap size가 작아지니 Hit rate가 떨어지면서 전체적인 응답속도가 느려졌다.

민감한 사용자라면 체감이 가능할 정도였다.

언제나 문제는 문제를 낳는 법이다.


원천적 해결방안

Java도 C/C++처럼 Create 와 Destroy 를 쌍으로 맞춰주면, GC 가 확실히 덜 발생된다.

하지만, 일일히 신경쓰며 작업해야 한다.

어떤 경우는 좀 더 효율적으로 로직을 변경하기도 했다. 시간이 적지 않게 걸렸다.


코드를 정비한 후에도 Heap size 를 원상태로 되돌리지는 않았다.

GC time을 일주일 정도 모니터링하며, 적정한 Heap size를 찾았다.

Memory SWAP time와 겹치는 순간이 없도록 메모리 운영전략도 새로 짰다.


DevOps + 개발자

Java 기반의 Server Application을 운영하면서 GC time 으로 고민해본적이 없다면 원인은 두가지다.


- 트래픽이 많지 않던지.

- 아직 실 서비스 운영을 안해본 사람이던지.


신규 시스템 구축만 오래 한 개발자들은 GC를 생소해한다.

그리고는 Java 객체를 서슴없이 사용한다.

트래픽이 몰렸을 때 Java VM 이 어떻게 작동하는지 거의 고려하지 않는다.


GC 에 대한 상세 내용은 아래 링크에 너무 잘 나와 있으니 소개만 해본다.


- Java Garbage Collection (Naver D2 페이지)

- Garbage Collection 모니터링 방법 (Naver D2 페이지)

- Garbage Collection 튜닝 (Naver D2 페이지)

- Garbage Collector Tuning as the First Step to Java Memory Usage Optimization (jalastic.com)


"네이버"는 GC 튜닝을 안해도 된다고 하지만 그건 운이 좋은 경우다.

운이 좋게도 훌륭한 개발자가 오랫동안 레거시 시스템을 유지보수하는 경우다.


대부분의 회사는 그렇지 않다.

있던 개발자는 떠나고, 새로운 개발자는 온다.

일정이 바빠서 개보수할 시간도 없이 이런 저런 기능을 덧붙여서 개발해서 한다.


GC time 문제는 왜 생길까?

레거시 시스템이란 진화를 멈춘 시스템이다.

진화 중이더라도 너무 커서 전체를 뒤집기 힘든 시스템이다.

그 속에서 소홀했던 Code 가 발견되는 건 자연스러운 일이다.


물론 훌륭한 Code Review 문화가 있다면 이런 일은 없다.

하지만, 항상 중위 80%는 그렇지 못하다.


질식할 정도로 몰려오는 트래픽을 그런 시스템으로 맞이해야 한다면 GC 문제는 결코 피할 수 없다.

사용자가 적을 때는 전혀 발생하지 않다가 사용자가 많아지면 예고없이 등장한다.

"과거에는 없었는데..." 라고 말해도 상황은 나아지지 않는다.


보통 사업이 잘되어서 시스템이 바쁠 때는 꽤 시간이 흐른 후다.

시스템을 뜯어보면 몇 번의 피봇팅 흔적이 드러나기도 한다.

그래서 이런 문제가 나타날 때쯤엔 개발했던 사람은 없다.

혹은 추가기능들이 잔뜩 붙어서 어디서부터 손을 대야 할지 알수도 없다.


다행히 원개발자가 남아 있거나, Cross Coding 이 가능하다면 이런 문제는 현저히 줄어든다.

아니라면 소스코드를 새로 짜고 싶은 욕구가 생길 수 밖에 없다.

하지만 오픈일정 때문에 그럴 여유가 없다면, 시스템은 점점 미궁속으로 빠지게 된다.


근본적인 대책?

내 경험으로 보면 GC 문제는 개발인력의 잦은 교체에서 발생한다.

그리고 잦은 교체는 사업의 불확실성에 기인한 경우가 많았다.


그런데 사업의 불확실성은 없앨 수 없다.

제일 좋은 건 개발인력이 자주 교체되지 않는 것이다.

하지만 진급, 직무 전환, 휴가, 자연이직, 퇴직 등을 생각한다면 완전 무교체도 불가능하다.

개발인력 교체에도 시스템이 잘 견딜 수 있도록 설계하고 운영하는 수 밖에 없다.


개발팀 문화는 이런 것을 책임지는 일이다.

물론 쉽지 않다. 최선을 다할 뿐이다.

최선을 다하면 상황은 개선된다.

하지만, 폼이 나지는 않는다.


FIN.


+ 최신글

+ 많이 본 글