티스토리 뷰

프로그램

Pooling

길나미 2016. 12. 27. 11:35

Pooling이란?

Wikipedia : Resource management 

https://en.wikipedia.org/wiki/Pooling_(resource_management)


자원을 일정갯수 미리 생성해두어 클라이언트의 요청이 있을때마다 해당자원을 꺼내쓰고 작업이 완료되면 자원을 반납하여 

다음 요청이 있을때까지 대기하는 일련의 작업상태. 

위키에서는 Resource management라는 의미로 해석을 했는데 우리말로 자원관리가 되려나? 좀 애매한 해석이다.




Object Pool Pattern


Pooling에 대한 기능은 소프트웨어 디자인패턴으로도 정의(?) 되어있다.



Object Pool은 소프트웨어 디자인 패턴으로서, 객체를 필요에 따라 생성하고 파괴하는 방식이 아닌,적절한 개수의 객체를 미리 사용 가능한 상태로 생성하여 이를 이용하는 방식이다.Client는 Pool에 객체를 요청하여 객체를 얻은 후, 업무를 수행한다. 얻어온 객체를 이용하여 업무 수행을 끝마친 후, 객체를 파괴하는 것이 아니라 Pool에게 돌려주어 다른 Cliet가 사용할 수 있도록 한다. Object Pooling은 객체 생성 비용이 크고,객체 생성 횟수가 많으며,평균적으로 사용되는 객체의 수가 적은 경우,높은 성능의 향상을 가져다 준다. 

http://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte:fdl:object_pooling





Connection Pool


데이터베이스에 자료를 저장하고 꺼내어 쓰는 프로젝트에서는 클라이언트의 요청이 있을때마다 DB 연결을 맺고 끊고 하는것이 얼마나 비용낭비인지 모른다. 그래서 이부분을 Pooling으로 만들어 DB커넥션에 대한 관리를 할수 있도록 한것을 Connection Pool이라고 칭한다.(라고 나름 정의를 해봄)


여기서 잠깐!

Apache에서는 Common-Pool과 Common-dbcp 라이브러리를 제공하고 있는데, Apache-tomcat에서도 Common-dbcp를 똑같이 제공하는데 Apache의 common-dbcp와 apache-tomcat의 common-dbcp는 무슨 차이일까?


https://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html

org.apache.tomcat.jdbc.pool 은 common-pool1.x를 대체할수 있고, 문제가 되었던 여러가지부분을 보완해서 만든것이라고 요약할 수 있겠다. 


여기서 말하는 문제가 되는 여러가지부분을 보완하고 성능이 개선되어 apache-common에서는 java7부터 적용할 수 있는 common-pool2를 내놓았으니 프로젝트 개발시 참고해야 한다. 


https://commons.apache.org/proper/commons-pool/

https://commons.apache.org/proper/commons-dbcp/



Apach Common-pool Common-dbcp는 어떻게 동작하는지 더 깊이 알아보자.


Connection dbcp pool을 제대로 사용하려면 설정사항을 잘 고려해야 한다.


( 출처 : https://commons.apache.org/proper/commons-dbcp/configuration.html)


ParameterDefaultDescription
initialSize0The initial number of connections that are created when the pool is started. 
Since: 1.2
maxTotal8The maximum number of active connections that can be allocated from this pool at the same time, or negative for no limit.
maxIdle8The maximum number of connections that can remain idle in the pool, without extra ones being released, or negative for no limit.
minIdle0The minimum number of connections that can remain idle in the pool, without extra ones being created, or zero to create none.
maxWaitMillisindefinitelyThe maximum number of milliseconds that the pool will wait (when there are no available connections) for a connection to be returned before throwing an exception, or -1 to wait indefinitely.

여기서 제일 중요한것을 maxToal, maxIdle 인데 이 값은 default 에서도 볼수 있듯이 동일한 값으로 설정하는것이 가장 좋다.

왜냐하면 대기중인 최대 커넥션 갯수와 최대커넥션 갯수의 차이가 클수록 새로 생성해야 할 커넥션이 많아지기 때문에 성능에 영향을 미칠 수가 있다. 


그리고 maxTotal을 설정할때 고려해야 할 사항은 DB에서 수용할 수 있는 Connection pool 갯수와 WAS 서버의 갯수를 잘 고려하여 설정해야 한다. 



Connection pool이 부족할경우 Exception 발생




Tomcat Thread


CGI 설명. 


톰캣도 내부적으로는 Thread pooling 방식을 사용한다.

설정방법과 테스트를 진행해보았을때 아래와 같다. 


설정방법


톰캣은 기본적으로 설치하게 되면 TOMCAT_HOME/conf/server.xml 안에서 설정할 수 있다. 

SpringBoot에서는 Tomcat-Embeded를 내장하고 있기 때문에 properties로 설정할 수 있다. 

(SpringBoot properties 옵션 참고 https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html)



톰캣의 maxThreads 설명중 

The maximum number of request processing threads to be created by this Connector, which therefore determines the maximum number of simultaneous requests that can be handled. If not specified, this attribute is set to 200.


출처 : https://tomcat.apache.org/tomcat-7.0-doc/config/http.html



테스트1. 

Server : max-thread = 1

Client : http request Number of Threads(users) = 40 * 2


결과 


Server 측 로그

max-thread를 1로 설정했으므로 http-nio-10088-exec-1 쓰레드 1개로만 로직이 처리되고

수행시간도 단일 쓰레드이므로 처리속도가 지연되는것을 확인할 수 있다. 

request first ~ last 수행시간 약 1분 40초

2017-02-06 10:54:14.785 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:15.793 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:16.797 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:17.800 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:18.804 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:19.811 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:20.815 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:21.820 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:22.824 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:23.831 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:24.833 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:25.841 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:26.843 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:27.850 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:28.853 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:54:29.857 DEBUG 29640 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() /


Client 측 로그 

Request에 대한 응답속도가 지연되는것을 확인할 수 있다. 

실제 웹사이트를 접속한 사용자라고 했을때 이만큼의 latency면 이미 창을 닫아버리고도 남을 시간이다. 






테스트2. 

Server : max-thread = 2

Client : http request Number of Threads(users) = 40 * 2


결과 


Server 측 로그

max-thread를 2로 설정했으므로 http-nio-10088-exec-1, http-nio-10088-exec-2 쓰레드 2개로 로직이 처리되고

수행시간도 단일 쓰레드로 처리했을때보다 빨라진걸 확인 할 수 있다.

request first ~ last 수행시간 약 40초

2017-02-06 10:59:51.072 DEBUG 35612 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:59:51.074 DEBUG 35612 [http-nio-10088-exec-2] --- : request.getRequestURI().toString() / 2017-02-06 10:59:52.979 DEBUG 35612 [http-nio-10088-exec-2] --- : request.getRequestURI().toString() / 2017-02-06 10:59:52.984 DEBUG 35612 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:59:53.987 DEBUG 35612 [http-nio-10088-exec-2] --- : request.getRequestURI().toString() / 2017-02-06 10:59:53.991 DEBUG 35612 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:59:54.993 DEBUG 35612 [http-nio-10088-exec-2] --- : request.getRequestURI().toString() / 2017-02-06 10:59:55.001 DEBUG 35612 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:59:56.001 DEBUG 35612 [http-nio-10088-exec-2] --- : request.getRequestURI().toString() / 2017-02-06 10:59:56.008 DEBUG 35612 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:59:57.012 DEBUG 35612 [http-nio-10088-exec-2] --- : request.getRequestURI().toString() / 2017-02-06 10:59:57.017 DEBUG 35612 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:59:58.017 DEBUG 35612 [http-nio-10088-exec-2] --- : request.getRequestURI().toString() / 2017-02-06 10:59:58.023 DEBUG 35612 [http-nio-10088-exec-1] --- : request.getRequestURI().toString() / 2017-02-06 10:59:59.021 DEBUG 35612 [http-nio-10088-exec-2] --- : request.getRequestURI().toString() /


Client 측 로그 

Request에 대한 응답속도가 지연되는고 있지만 latency에 대한 시간을 thread1과 비교했을때응답속도가 2배 빨라진걸 확인 할 수 있다.





간단한 테스트를 통해 thread에 대한 처리과정을 로그로 확인해보았지만 

실제 라이브되는 서비스라면 max-thread 갯수보다 request 수가 많게됐을경우 

All threads (150) are currently busy, waiting. Increase maxThreads (150) or check the servlet status

와 같은 오류를 뱉어내며 서버가 다운될수 있기 때문에 초기 세팅시 max-thread에 대한 수치를 적절하게 잘 설정해야 한다.





Thread Pool


개발을 하다보면 많은 작업을 한꺼번에 빠르게 수행해야 할때가 있다. 

경험상 예를들어 sms발송, mobile push, logging 등의 기능을 구현할때 그런 어려움을 겪었었는데 그때 가장 유연한 방법이 Thread Pool을 통한 방식이었다. 


Thread pooling 방식으로 구현했을때와 요청시마다 thread 생성하고 작업이 완료되면 thread를 종료하는 반복작업을 했을때의 resource를 비교해볼 수 있다.


쓰레드 생성~종료  => 쓰레드의 Live 갯수(105)와 peak 갯수(110) 확인, Total Started Thread(12074) 확인






쓰레드 초기 생성 - 대기 - 실행 - 대기  => 쓰레드의 Live 갯수(105)와 peak 갯수(134) 확인, Total Started Thread(2600) 확인



실제 Thread 작업후 자원반환에 대한 시간을 측정했을때도 Pooling기법이 훨씬 빠른것을 확인할 수 있었다



댓글