brunch

You can make anything
by writing

C.S.Lewis

by 에단박씨 Apr 19. 2018

WAS worker thread 부족에 대한 방안?!

Non Blocking IO와 Spring DeferredResult!

굳이 개발자가 아니더라도, 뉴스나 신문을 통해 '접속폭주로 인한 서버장애' 라는 기사는 한번쯤은 들은 적이 있을 것입니다. 프로젝트를 진행함에 있어 개발자와 인프라 담당자들은 트래픽을 예측하여 서버를 이중화 혹은 삼중화 구성 후, LB(Load Balancing)로 부하분산을 고려합니다. 하지만, 그런 예측도 수십배이상 상회하는 거래(일명 DDos급거래)가 유입되면, 아무리 훌륭한 구성의 인프라 및 어플리케이션이라고 해도 버틸 재간이 없습니다. (물론 예측을 잘못한거 아니냐고 하면 할말은 없겠지만, 그렇다고 무작정 비싼 서버를 구성해놓을 수는 없습니다.)

그렇기 때문에 특정 수치 이상의 거래가 들어오게 되면, 서버가 자동으로 scale out 될 수 있는 클라우드 환경이 지속적으로 각광을 받고 있는 것이며, 많은 회사들이 클라우드를 사용하는 이유도 여기에 있을 것입니다. 


그럼 클라우드 환경으로 가면 폭주로 인한 서버장애는 없는거네요

네 이론상으로는 그렇겠죠. 하지만, 현실은 그렇지 않습니다. 아무리 하드웨어가 받쳐준다고 해도 어플리케이션에서 지속적으로 병복이 발생하여, 컨테이너(WAS)의 worker-thread가 부족하게 된다면, 서버장애가 지속적으로 발생할 수 밖에 없을 것이고, 그로 인한 지속적인 scale out으로 인해 엄청난 비용의 부담을 떠 안을 수 밖에 없을 것입니다. 그 대표적인 사례가 지난 번 지진으로 인한 국민안전처 접속장애가 될 수 있겠네요...클라우드로 가서 80배 성능향상시켰다고 하는데도 그 다음날 바로 장애.... 


관련기사 : http://www.etnews.com/20171121000393


그렇기 때문에 거래폭주로 인한 장애를 최소화하기 위해서는 인프라 뿐만 아니라 어플리케이션에서의 아키텍처 개선도 고려되어야만 합니다. 

근래에 들어 이런 어플리케이션 영역에 대한 성능 및 운영안정화 개선 방안으로 MicroService Architecture(MSA)와 Non Blocking IO 아키텍처에 대한 관심이 집중되고 있는 상황이며, Java진영 같은 경우에는 Spring의 DeferredResult와 CompletableFuture가 대표적인 Non Blocking IO구조로 관심을 받고 있는 상황입니다. Spring을 오래 쓰셨고 잘 아시는 분들도 이 부분에 대해서는 잘 모르시는 경우가 있습니다. 그래서 지금부터DeferredResult를 생소해하시는 분들을 위해 기존 Blocking IO와의 비교를 통해 장점을 이야기 해보도록 하겠습니다. 



환경

- Window10 intel i7-6500 cpu 16G RAM 
- jdk8 + intellij + SpringBoot 2.0.0
- Apache jmeter
- Visual VM

조건

- SpringBoot 기반 일반 Controller 메소드와 DeferredResult로 구성된 메소드로 랜덤 Sleep처리되는 Service를 호출
- Embedded Tomcat 의 worker thread 갯수는 default.
- 각메소드별 호출은 800, 900, 1000user로 10번 반복처리되도록 함. 

https://gitlab.com/heracul/eth.pak.demo.nonblocking

//일반 Blocking IO처리 소스(일반spring mvc controller)
@RequestMapping("/sync")
    public ResponseEntity<String> block() {
        logger.debug(">>>>> Start normal");
        try {
            nonBlockingService.doSleep();
        } catch (Exception e) {
            throw e;
        }
        logger.debug(">>>>> End normal");
        return ResponseEntity.ok("OK");
    }
//DeferredResult를 통한 Non blocking IO처리
@RequestMapping("/callDeferredResult")
    public DeferredResult<String> nonBlock() {
        final DeferredResult<String> deferredResult = new DeferredResult();
        logger.debug(">>>>> Start DeferredResult");
        new Thread(()->{
            try {
                nonBlockingService.doSleep();
                deferredResult.setResult("OK");
            } catch (Exception e) {
                throw e;
            }
        }).start();
        logger.debug(">>>>> End DeferredResult");
        return deferredResult;
    }


결과

1) Spring MVC기반 Bloking IO(샘플별 average만 보시면됨)

   - User가 늘어나도 처리하는 쓰레드는 수 동일함. (WAS의 HTTP Worker Thread를 쓰기때문)

   - 당연히 User수가 늘어날수록 속도는 급격히 저하됨. (하지만 CPU랑 메모리는 놀고 있음. 유입될 수 있는 쓰레드가 한정되어 있기 때문)

Blocking IO - 800user로 10번



Blocking IO - 900user로 10번



Blocking IO -1000user로 10번


2) Spring MVC에 DeferedResult구성 기반 Non Blocking IO처리(샘플별 average만 보시면됨)

    - 접속 User수에 비례하여 Active Thread가 급격히 늘어남. (자원소모심함)

    - User수에 관계없이 elapse time은 균일하며, 거의 병렬처리이다 보니 전체 건수에 대한 처리속도가 엄청 빠름

Non Blocking IO  - 800user로 10번
Non Blocking IO - 900user로 10번


Non Blocking IO - 1000user로 10번


결론

Servlet3.0이상에서 동작하는 Spring의 DeferredResult나 CompletableFuture 같은 Non Blocking IO구성은 WAS의 worker thread를 @Controller전까지만 사용하고, 그 뒤로는 별도의 thread를 생성함으로서 WAS가 listen할 수 있는 thread를 항상 균일하게 확보해주어, 거래폭주가 발생하더라도  listen하는 thread가 없어 발생할 수 있는 'Connection refuse' 또는 Client내 Connection timeout을 막을 수  있습니다. (걍 친절하게 다 받아쥼@_@)

하지만, 명심해야 할 것이 이런 Non Blockging IO구성이 WAS의 장애를 막아주는 것은 절대 아니라는 점입니다. 도리어 병렬처리로 인해 병복구간에 더 많은 부하를 발생시켜 기존보다 더 빨리 장애를  발생시킬 수도 있습니다. 

또한 이런 NonBlocking IO의  Async개념을 잘못 이해하시고 Request를 받고 끝내는 것으로 아시는 분도 계시던데, 그건 다양한 곳에서 사용되는 Async의 개념으로 인해 발생하는 착오라고 말씀드릴 수 있겠습니다. NonBlocking IO구성도 결국에는 HTTP 프로토콜 처리기반 서비스 입니다. TCP Socket과 같이 Async call을 할 수 있다는 것이 아니라는 뜻입니다. (물론 WebSocket기능을 사용하면 Server to client의 PUSH구조로 할 수는 있겠지만, 그렇기 위해서는 Client도 listen할 수 있는 구성이 되어하므로 패스.) 단지, Spring의 WebAsyncManager가 요청들어온 HTTP Request 를 기존 WAS worker thread로부터 임대(?)하고 있다가 비즈니스 로직에서 


deferredResult.setResult("객체 또는 value");


하는 시점에 call back처리 되는 것 입니다. 그래서 지정된 시간내에 비즈니스로직에서 set을 해주지 않으면 아래와 같이 warning 이 발생하게 됩니다. 


15:57:45.786 [http-nio-8081-exec-2] WARN  o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolved exception caused by Handler execution: org.springframework.web.context.request.async.AsyncRequestTimeoutException

※ 관련 properties : spring.mvc.async.request-timeout


다시 정리하자면, DeferredResult나 CompletableFuture를 사용하는 것이 WAS의 HTTP worker thread를 사용하지 않는 것일뿐, HTTP Connection에 대한 close를 의미하는 것은 아니며, 별도의 WebAsyncManager를 통해 관리된 Connection이 DeferredResult.set()을 하는 순간, 이벤트 처리로 response를 client에게 전달한다는 뜻입니다.  


그럼 이거 왜 써야 해요? 아무 필요없는 거 잖아요?


노우노우! 통상적으로 병복이 자주 발생하는 구간을 보면 lock이 자주 발생한다던지(RDB에서 동일row에 대한 update, select for update등등), 동일파일에 append를 하는 것과 같은 작업을 자주 할 경우 발생합니다. 이건 기존 Blocking IO구성에서도 개선해야만 하는 로직입니다.  다만, 굳이 더 적합한 용도를 판단해보자면, 업무의 용도가 proxy처리 나 허브와 같은 중계자역할 위주이거나, slow query의 Select처리 위주의 업무라고 하면, 엄청난 performance를 기대할 수 있습니다. (물론 자원은 엄청 사용하기 때문에 그건 고려해야함)

실제로 특정 시스템의 허브구성을 위해 Request로 들어온 json에 추가정보를 select후,  queue에 넣고 타 시스템으로 HTTP발송하는 PoC성의 springboot어플리케이션을 구성했었는데,  실수로 1600~1700tps의 거래를 발생시켰음에도 불구하고 아무 문제 없이 큐잉 후 처리했었습니다. (5년전 쯤 특정 증권사 tps 요구사항 1500이었음을 감안하면 대충 감이 오시죵 ^^; 물론 복합로직과의 비교는 불가)


이처럼 업무에 대한 명확한 요건을 확인한 다음 그 환경에 맞게  DeferredResult와 같은 NonBlocking IO처리 구성을 한다면, 왠만한 부하정도는 여유롭게 처리할 수 있을 겁니다. 


사족

얼마 전에 Springboot 2.0이 release되었습니다. 1.5와 큰 차이점은 webflux stack 과 기존 servlet stack으로 나눠진다는 점입니다. 그 중 webflux는 Servlet을 사용하지 않는 진정한 NonBlocking IO처리라고 할 수 있는데....이게 말이죠...DeferredResult와의 성능차이가 어마어마합니다. 1000user로 10번 거래를 했을 때 DeferredResult로 24초 나오던 것이 Webflux로 6초만에 처리되었습니다...

물론 단순거래테스트이고 병복구간이 전혀 없기 때문이긴 하겠지만, 그래도 와아~ 소리가 나오기에는 충분했습니다. 다음에 기회가 되면 Webflux 와 DeferredResult를 비교한 주제로 글을 다시 올리도록 할께요 ^^



참고문헌

http://callistaenterprise.se/blogg/teknik/2014/04/22/c10k-developing-non-blocking-rest-services-with-spring-mvc

https://www.nurkiewicz.com/2013/03/deferredresult-asynchronous-processing.html


브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari