카테고리 보관물: Java basic

대용량 Job 처리시 Hibernate Session Memory leak 경험

안녕하세요. 이경일입니다.

그동안 illuminati 개발을 하느라고 블로그에 정리를 하는 것을 소홀히 했네요.

Project illuminati git : https://github.com/LeeKyoungIl/illuminati <- 허접하지만..별 좀 주세요… (굽신굽신)

오늘은 hibernate를 사용하는데 대용량 Job 처리 시에 발생할 수 있는 OOM 에 관해서 정리를 해보려고 합니다.

제가 하는 업무에서 외부와 연동하는 Job을 돌릴 일이 있었는데요. 종종 오류가 발생을 하는 현상이 발견되었습니다.

해당 Job은 실행이 될 때마다 대상의 개수가 변하기 때문에 작게는 몇백 개 많게는 몇십만 개가 넘을 때도 있었죠.

현상은 몇백 개 돌 때는 괜찮은데 10만 개가 넘어가면 오류가 발생을 하더라구요…

오류부터 보겠습니다.

 

 

OOM 이 발생하고 있는 것을 볼 수 있습니다.

해당 잡은 하루에 한 번 돌고 한번 돌면 한 시간 이상을 그냥 돌아가는 대용량의 잡이죠…

Job 서버라 10만 개가 넘어갈 무렵 heap Dump를 떠 보았습니다.

보니 메모리 릭이 발생하고 있네요.

전체 Heap의 74%를 점유하고 있는 부분을 찾아보았습니다. StatefulPersistenceContext 에 메모리가 누적이 되어있어서 자세하게 어떤 Object 인지 찾아보았습니다.

 

헐… HashMap 노드 개수 보이시나요?

 

동일한 이슈가 있나 구글링을 한 결과 발견을 했고.

(구글신 없으면 개발 어떻게 할지 막막하네요 ㅜㅜ…)

대강 핵심만 정리해 보면 Hibernate session 이 열려서 객체를 영속화 하면 당연히 cache를 하는데요. job의 경우 보통은 jenkins를 이용해서 실행을 시키면 빌드를 하고 -> Job 실행 -> 종료 이런 라이프 사이클을 가지죠 그러면 ORM을 사용하면 Hibernate session 은 Job이 실행을 하고 종료를 할 때까지 살아 있다고 보면 됩니다. 따라서 해당 Job에서 100개던 20만 개던 데이터를 DB에서 영속화 시키는 object를 전부다 cache 해서 가지고 있겠죠. 이게 Heap 용량을 넘어가지 않는다면 잡이 종료되며 클리어가 되겠지만 넘어가면 OOM이 발생하며 Job이 중지가 되는 상황인 겁니다. 따라서 session을 flush 하고 clear를 해줘야 하는 이슈가 생기는 것이죠.

 


따라서 코드에 해당 부분을 추가해 주었습니다.

 

결과는 짠~? 해결 되었습니다.

오늘도 이렇게 하루를 넘기는군요.

 

 

나만 몰랐던 Http Delete Method payload body 문제

오늘 해당 문제로 좀 삽질을 했습니다.  (다 저의 무지함 때문에 발생한 일이지만… ㅜㅜ)

간단한 Delete 요청을 처리하기 위해 동료 개발자와 협업을 하고 있었죠.  서로 다른 도메인을 담당하기 때문에 직접 해당 DB에 붙어 CRUD를 날릴 수가 없었고
시스템 간의 의존도를 없애기 위해 동료 개발자는 API를 만들어 주고 저는 해당 API를 이용해서 특정 정보를 CRUD 하는 작업을 하고 있었죠.
CRU까진 완료된 상황 이제 D (Delete)가 남아서 Http의 Delete 메서드를 이용해서 Pqyload Body에는 간단한 ID 정보만 실어서 요청해주면 끝.

집에 가야지… 했는데 해당 API요청이 오류가 나는 것이 었습니다.

해당 코드입니다. 저희는 Grails + Groovy를 사용 중인데 Grails의 restBuilder를 사용해서 Json으로 변환된 Map을 이용해 Rest API 호출을 하는 간단한 코드입니다.

restBuilder 는 Spring 의 RestTemplate 으로 구현되어있습니다.

이제 동료 개발자가 만든 받는 쪽 소스를 보면

이렇게 간단하게 Delete 메서드로 요청한 Json String을 RequestBody로 파싱 해서 객체로 만들어 주는 간단한 Controller 코드입니다.
하지만 요청을 하면 오류…… API 서버 로그를 보면

이렇게 오류가 나오고 있었죠.
처음에는 또 Grails 가 이상하게 구현이 되어있는 줄 알고 Break 포인트 걸어가며 한줄한줄 소스를 까 보았는데 문제가 없는 상황…

그래서 구글링을 해서 Http Spec을 확인…

아 젠장 ㅜㅜ Spec먼저 보고 할껄…

허접 번역을 하면 “Delete 요청은 payload body 가 필요가 없다.  Delete 메서드로 payload body를 실어 요청을 하면 요청이 거절될 수 있다.

일단 API 서버가 제 담당이 아니지만 당장 일정이 있기에 최소한의 수정으로 끝내고자 일단 Delete Method를 이용해 payload body 를 보낼 방법을 찾기 시작했습니다.

일단 나온것이

톰켓의 컨넥터 설정을 바꿔서 DELETE 메서드와 같이 오는 payload body를 파싱 하는 방법 , 해당 코드는 Tomcat를 사용할때 설정으로 하는 방법이고

Spring Boot 의 embedded tomcat 을 사용한다면.

대략 이런식으로 해주면 됩니다.

하지만 위 방법으로 결국 동작 하지 않았습니다…. 결국 그냥 동료에게 양해를 구하고 …

그냥 내가 직접 API서버의 코드를 PathVariable 로 ID 값을 받도록 수정을 했습니다.

이상으로 나만 모르는 Delete method 사용법이었습니다.

오늘의 교훈… 머리가 나쁘면 몸이 고생하며 괜한 야근을 한다… ㅜㅜ 스펙을 읽어보고 개발을 하자.

그런데 의문점 하나는 Swagger 에서는 delete method 로 payload body 에 json 데이타를 같이 보내면
동작을 한다는거…. 이거 소스한번 까봐야 겠어요.. 지금은 너무 힘들어서 일단 여기까지. ㅜㅜ

Too many open files 해결하기

IO 관련해서 개발을 하다보면 자주 마주치는 부분중 하나가 이 오류일 것이다.

java.io.IOException: Too many open files

일단 해당 메시지로 구글 검색을 하면 대부분 해결책이

“시스템의 open files 를 올려라 리눅스의 경우 ulimit -n 65535” 이정도 및 limit.conf 파일 수정 하는 것이
나오는 것이 대부분 일것이다.

하지만 그전에 프로그램에서 뭔가 파일을 열고 닫는 부분에서 버그가 있어야 하지 않을까? 라는 것으로 접근하는 것이
가장 우선시 되어야 할 사항 이라고 생각한다.

일단 이렇게 접근 하기 위해서는 프로그램이 실행이 될때 (어떠한 액션이 발생할때) 지금 열린 파일의 갯수를 알아야 할것이 아닌가?

os 가 linux 라고 가정 할때 대상 프로세스에서 열린 파일의 수를 실시간으로 확인이 가능한 간단한 스크립트를 소개하겠다.

가령 대상 프로세스가 java 프로세스라고 가정하면

shell # ps aux | grep java

해당 프로세스 리스트를 확인하면

아래와 같이 593228 라는 프로세스 아이디가 나온다.

그러면 아래처럼 linux shell 상에서 명령어를 실행하면…

라고 실행하면 2초 단위로 해당 프로세스에서 열린 파일의 갯수가 출력되니 디버깅에 참고하기 바란다.

ConcurrentHashMap 의 lock 거는 기법

일단 ConcurrentHashMap 과 HashMap 의 차이점은 다 알고 있다는 전제하에 포스팅을 작성 한다.

이 포스팅을 하게된 이유는 사실 어떤분이 나에게 ConcurrentHashMap 과 HashMap 의 차이점을 아냐고 물어봤고
물론 알고 있어서 쏼라쏼라 대답을 했지만 (Multi-Threaded 환경에서 thread safe 여부) 거기에서 끝나지 않고
또 물어보시더라 ConcurrentHashMap 이 왜 Thread 에 safe 한지? 어떤 원리인지 아세요?

그래서 나는 사실 정확하게 알진 못했지만 대략 MySQL 의 innodb 처럼 row-level lock 걸듯이 접근하고 있는 해당 Key 값에
lock을 걸지 않을까요? 라고 대답을 했다.

이 대답은 후에 찾아 보니 반은 맞고 반은 틀리더라…

그리고 다시한번 정확하게 정리한다..

——————————————————————————————————

보통은 우리가 Multi-threaded 환경에서 key-value 저장소가 필요할때 자바에서 사용하는 것이

크게

1. HashTable
2. HashMap
3. ConcurrentHashMap

이렇게 있는데 파악을 하기 위해서 각 자바 source 를 까보았다.
가장 중요한 Get, Set 부분만 보면

1. HashTable


public synchronized V More ...get(Object key)
public synchronized V More ...put(K key, V value)

보이는가? 메소드 레벨에서 synchronized 를 걸어 버린다.

2. HashMap

얘는 볼것도 없이 synchronized 가 없다. 따라서 속도는 빠르지만 Multi-threaded 환경에서 그냥 쓰면 여러 Thread 에서 동시 접근시 Exception 이 난다.

3. ConcurrentHashMap

얘는 특이하게 synchronized 가 아니라 lock 을 사용한다.


V More ...put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry[] tab = table;
.
.
.

처음에 이 부분만 보고 아싸 내 말이 맞았구나 했다. 하지만 찾아 보니

비슷은한데 CurrentHashMap 은 Map 자체를 Currency 레벨로 영역을 쪼개서 (기본이 16개)
해당 영역별로 lock을 거는 것이었다. 이 Lock 이름은 ReentrantLock 이라고 한다.

소스에서

CurrentHashMap 생성자중에


public More ...ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
...
}

이런놈이 있더라… 하아…

역시 갈길이 멀다.. 그래도 좋은거 하나 확실하게 배웠다.

jmx 를 이용한 JVM 모니터링 사용하는 방법

JVM 상태를 모니터링할때 간단하게 사용할수 있는것이 바로 JMX 이다.
java 를 설치하고 기본적으로 깔려있는 비주얼 jvm으로 간단하게 확인 가능하다

java 어플리케이션을 실행전에 해당 옵션을 추가해 주면 된다.

export JAVA_OPTS=”$JAVA_OPTS -Dcom.sun.management.jmxremote=true”
export JAVA_OPTS=”$JAVA_OPTS -Dcom.sun.management.jmxremote.port=포트”
export JAVA_OPTS=”$JAVA_OPTS -Dcom.sun.management.jmxremote.ssl=false”
export JAVA_OPTS=”$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=false”
export JAVA_OPTS=”$JAVA_OPTS -Djava.rmi.server.hostname=서버아이피”

해당 설정은 인증없이 바로 JMX 에 접근할수 있으니 ACL 등을 이용해서 접근 권한을 설정하도록 하고

만약 인증이 필요하면 jmxremote.access과 jmxremote.password 파일이 필요하다.

jmxremote.access : 사용자 권한
jmxremote.password : 사용자 비밀번호

jmxremote.access는 $JAVA_HOME/jre/lib/management/ 에 위치해 있다.
jmxremote.password는 $JAVA_HOME/jre/lib/management/에 템플릿파일이 있으니 복사해서 쓰면 된다.
(chmod 600 jmxremote.password 권한 설정 필수 읽기만 가능하도록)

다음 해당 값을 바꿔준다.
export JAVA_OPTS=”$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=true”

테스트 결과 어느정도 고 부하가 있는 서버에서도 큰 문제없이 사용이 가능했었다.
그래도 꼭 테스트를 하고 사용하도록 하자

[jsp] jsp 가 servlet 으로 변환될때 중요한 부분

Jsp 는 컨테이너에서 돌아갈떄 Servlet 으로 변환되어 Singleton 으로 돌아간다.

최근에 나와 관련있는 사이트에서 딱봐도 멀티 쓰레드 환경에서의 자원 동시 접근 문제가 보여서

해당 사이트 관리하고 있는 업체에게 소스 코드를 요구 했다.

코드를 보니

라고 되어있었다;

Jsp 가 Servlet 으로 변환될때 !를 붙이면 인스턴스변수 즉 전역 변수로 변환이 된다.

Servlet 은 컨테이너에서 Singleton 으로 돌아가기 때문에 멀티 쓰레드 환경에서 문제가 발생한것이었다.

업체에 코드 수정을 요구했고 해당 문제는 사라졌다.

Jsp 에서는 <% 와 <%! 가 각각 다르게 Servlet 으로 변환된다는 사실 꼭 알고 있도록 하자.

No X11 DISPLAY variable was set, but this program performed an operation which requires it

Mac 에서 작성한 Java 프로젝트를 Jar 로 만든뒤 Linux 서버에 올려서 jar 파일을 바로 실행을 시켰다.

스크린샷 2014-08-23 오후 5.25.12

어? 이런 오류가 나서 놀랐다.

No X11 DISPLAY variable was set, but this program performed an operation which requires it

이유는 Linux DISPLAY 환경 설정이 되어있지 않아서 문제가 발생 했다는건데 이건 서버라 X윈도우 쓰지 않기 때문에

그냥 export DISPLAY=:0.0 로 처리해서 해결

Centos 6.5 에 Java 1.4 설치 하기

회사에서 중요한 프로젝트를 하는데

기존 시스템이 java 1.4 를 쓰는 관계로 Test bed 구축을 위해 개발 서버에 centos 6.5 를 깔고 오라클 홈페이지를 가서 java 1.4 를 다운로드 받았다.

bin 파일 이었는데 설치를 하려고 실행 시키고 보니

/lib/ld-linux.so.2: bad ELF interpreter: No such file or directory -_- 이런 오류가…

구글링 하다 보니 ld-linux.so.2 를 설치하라고 하는 이야기가 있어서

yum install ld-linux.so.2 로 설치 완료후 다시 설치해 보니

성공!

그나저나 java 1.4 …. 아놔.. API 문서가 오라클 홈페이지에 떠있는것도 없군요.
다운 받아서 압축 풀고 봐야 합니다..

Java 를 이용한 간단한 웹 서버 만들기

Request 모델 Request.java

Response 모델 Response.java

HttpServer 클래스 HttpServer.java