본문 바로가기

Spring/Spring

Controller 1개가 어떻게 수 많은 Request를 처리하는가? (spring mvc, tomcat thread, singleton bean)

반응형

Controller 1개가 어떻게 수 많은 Request를 처리하는가

배경 의식의 흐름

Controller는 한 개인가?

지금와서 생각하면 조금 바보같았지만, 저런 질문이 떠오른 이유는 아래와 같습니다.

스프링에서 웹 애플리케이션을 개발할 때, (대부분의 경우에) 스프링 빈(Bean)으로 객체를 생성합니다.

Bean의 스코프를 prototype으로 변경하면 HTTP Request 마다 해당 빈을 새롭게 생성해서 처리하도록 할 수 있지만, 그렇게 하는 경우를 거의 본 적이 없습니다.

그러면 내가 만든 Controller(Bean) 하나가 1만 개의 요청이든 10만 개의 요청이든 처리를 한다는 얘긴데, 이게 감당가능한가? 이런 생각이 갑자기 스쳐지나가면서 의문이 생겼습니다.

혹시 여러 컨트롤러가 만들어지는 것은 아닐까 의문을 품고 조금 찾아보니 우선은 Controller 객체(Bean)는 한 개가 생성되는 맞고 그 하나의 컨트롤러를 사용하는게 맞았습니다. (일단 OK)

tomcat worker(?) thread가 200개라던데?

그리고 WAS마다 다르겠지만 학습할 때 많이 쓰이는 톰캣(tomcat)... 이게 옛 기억을 더듬었을 때 default로 HTTP Request를 받아주는 스레드(Thread)가 200개라 들었던게 생각났습니다.

찾아보니까 쓰레드 풀에 생성될 수 있는 쓰레드 의 수가 MAX가 있는 것이고, 실질적으로는 최소로 유지될(idle 상태) 쓰레드 수(10개?)가 있고, 요청이 많아지면 그에 따라 큐에 쌓였다가 쓰레드를 만드는 등 일반적인 쓰레드풀의 동작을 하고 있었습니다.

그래도 결국에는 동시에 10만건이 오면 쓰레드풀 동작에 따라 200개의 쓰레드가 작업을 할 수도 있는 것이라고 생각했습니다.

그러면 어쨋든 10만 개의 Request가 있다면 Controller 1개가 전부다 처리하는거잖아?

네. 저런 위험한(?) 생각을 해버렸습니다.

Request별로 쓰레드가 따로 생성되고 각각의 Context를 갖는다는 것을 인지하고 있었지만, 그 쓰레드들이 Controller 객체를 공유하니까 이거 문젠데? 라는 생각을 말이죠.

어떻게 돌아가는지 보자

놓치고 있는게 있는지 살펴본다

위와 같은 의식의 흐름 끝에 도달한 결론인 "Controller 객체 한개가 10만 개의 Request를 처리하는 건 문제다." 라는 것을 갖고 뭔가 빠진게 있나 고민하기 시작했습니다.

왜냐하면 스프링은 그 10만 개의 Request를 하나의 Controller가 이슈 없이 잘 처리하고 있으니까 제가 이상한게 분명한 것 같았기 때문입니다.

나와 같은 의문을 갖는 사람들이 예전에도 있었다

위의 내용들을 간략하게 요약하면, 결국에는 내가 생성한 Controller 클래스에 대한 정보가 JVM 메모리 영역 중에 '메소드 영역'에 올라간다는데에 있다는 것입니다.

무슨 얘기냐면, Controller 객체를 생성하면 객체는 힙에 생성되지만 해당 클래스의 정보(메소드라면 메소드 처리 로직(명령들))는 메소드영역에 생성된다는 것입니다.

결론적으로 JVM 구조를 언급했던 지난 포스트를 참고했을 때, 힙영역이든 메소드영역이든 결국에 모든 쓰레드가 객체의 메서드(바이너리 코드 자체이자 메모리)를 공유할 수 있다는 것일 뿐 공유를 위해 블록이 되는 것은 아닌 것입니다.

이게 내 결론이다

바보같은 질문

"공유"라는 말이 단순하게 같이 쓸 수 있다! 이렇게 생각하고 끝냈어야하는데 바보같이 공유하면 동기화를 해야지 이러면서 lock을 걸고... lock이면 병목... 이런 생각을 했던게 문제였습니다.

그냥 말 그대로 공유였습니다. 내부적으로 상태를 갖는게 없으니 그냥 메소드 호출만 하기 때문에 동기화할 필요가 없고 처리 로직만 쓰이기 때문에 1만 개의 요청이든 10만 개의 요청이든 상관없다는 얘기인 것이지요.

(단, 매번 Controller 객체를 새로 생성하는 방식(scope=prototype)은 언제 쓰이는지 여전히 의문입니다.)

앞으로 해야할 일

  • 스프링 Bean을 생성할 때, 절대로 상태를 갖는 Bean을 생성하지 말아야 한다.
    • 스프링 Bean이 상태를 갖게 되었을 때는 그 상태를 공유하는 모든 쓰레드들로 부터 안전할 수 있게 동기화를 해줘야하고 동기화를 하는 순간 싱글톤으로써의 혜택이 날아간다고 봐야하기 때문입니다.

의식의 흐름

  • Controller(Bean) 하나가 어떻게 수 많은 쓰레드로부터 처리를 할 수 있는지 알게되었다.
  • 그런데 이제 생각이 확장되어 '톰캣 MaxThreadPoolSize는 200개인데 DBCP인 HikariCP는 왜 maximumPoolSize가 default 10개인가에 대한 고민이 시작되었다. (ThreadPool이랑 ConnectionPool이 다른 것 같긴 한데...)
    • 실질적인 쓰레드의 수가 아니라 DB 연결에 쓰이는 Connection 객체를 공유하는 것이라 개수가 다르다는 결론!
  • 톰캣 쓰레드는 왜 200개까지나 늘릴까?, 그 이상 튜닝도 하는거로 아는데...? 어차피 서버 코어 쓰레드 수 이상으로는 작업 못하지 않나? 하는 생각도 했다. (톰캣이 blocking 방식이라 여러 요청을 처리하기 위해 그렇다는 것 같은데...) -> (수정합니다. tomcat 8부터는 nio 즉, non-blocking 방식을 쓴다고 합니다. 댓글 참조! 그러고 보니 스프링 부트 애플리케이션 실행하면 아래와 같이 나왔던 것도 같다. nio 라고 써있네!)
...Starting ProtocolHandler ["http-nio-8080"] ...

그런데 상기의 고민들은 여기서의 주제와 약간 벗어나기 때문에 일단은 마무리를 짓는다.

추후에 저 고민에 대한 포스팅이 있기를 바라며...

출처 : https://m.blog.naver.com/tmondev/220731906490

 

반응형
  • Favicon of https://brocess.tistory.com BlogIcon brocess 2020.02.18 18:32 신고

    tomcat 8 버전부터 connector의 방식은 bio가 아니라 nio입니다.

  • Favicon of https://ssaemo.tistory.com BlogIcon Hojong 2020.05.01 03:05 신고

    안녕하세요 좋은 글 잘 읽었습니다
    저도 비슷한 내용을 공부 중이어서, 몇 가지 코멘트 남겨봅니다
    - tomcat max thread pool size가 connection max pool size보다 작은 이유는, 모든 요청이 DB를 사용하는 것은 아니기 때문이라는 내용을 본 적이 있습니다 (서버에 따라 다르겠지만)
    - tomcat thread pool size는 각자 성능 측정을 하고 필요에 따라 조절해야하므로 (가이드도 그렇게 되어있고) default value가 200인 것은 그저 기본값일 뿐이라고 생각하면 될 것 같아요
    좋은 글 감사합니다

    • 도움 주셔서 감사합니다!
      모든 요청이 DB에 요청하진 않겠지만 대부분의 요청이 DB를 쓰지 않나 하는 의문도 있습니다. (현실적으로요...)
      제가 고민해볼 문제에 도움을 주셔서 다시 한 번 감사의 말씀을 드립니다!

    • Favicon of https://ssaemo.tistory.com BlogIcon Hojong 2020.05.03 11:56 신고

      저도 같은 의문이 들었었는데, 지금 생각해보니 요청을 받았을 때 DB가 아닌 다른 서버에 요청을 보내는 케이스는 자주 있을 것 같네요. 사내 또는 사외 API 서버로 나뉠 수 있겠습니다
      저도 많은 공부가 되었습니다 감사합니다~

    • 또 한 번 감사의 말씀 전합니다!

  • Favicon of https://myage.tistory.com BlogIcon 바다21 2020.05.04 18:49 신고

    일단.. 쓰레드풀과 DB커넥션풀의 개수가 다른건.... 쓰레드는 쓰레드고.. 커넥션은 메모리상에 존재하는 자바객체이기 때문입니다. 톰캣의 쓰레드 입장에서는 커넥션 객체도 일종의 자원인거죠. 쓰레드라는건 어차피 머신의 스펙을 뛰어넘지 못합니다. 가진 시간과 자원을 쪼개서 쓰는 것 뿐이죠. 근데 그걸 크게 잡아놓으면.. 시간이 걸리더라도 요청을 다 처리하겠다는 의미이고... 그걸 작게 잡아놓으면 제한된 요청만 처리하고 나머지는 안하겠다.. 이런 뜻입니다.

    컴퓨터에서 프로그램이 어떻게 돌아가는가? 내지는 Java 객체가 메모리 상에서 어떻게 존재하고 실행되는가? CPU? OS가 프로세스나 쓰레드를 어떻게 처리하는가? 등의 내용을 추가로 탐구해보시면... 이해에 많은 도움이 되실거 같아요. 요새는 java를 배움과 동시에 spring 을 배우는 경우가 대부분이라 처음부터 너무나 추상화된 이미지를 머리에 넣고 개발하시는 분들이 많은 것 같아요. 알고보면 생각보다 어렵지 않을 수도 있습니다. 블로그에 파이팅이 넘쳐서.. 저도 모르게 파이팅 넘치는 댓글을 달게되네요.

    암튼.. 스프링부트 관련된 내용 찾다가 들어와서 우연히 본 글에 댓글 남겨봅니다. 포스팅해주신 스프링부트 관련자료는 많은 도움이 되었네요. 감사합니다! 그럼 열공하세요~!

    • 선배님이 해주시는 말씀같아 이해도 잘 되고 와닿고 동기부여가 많이 됩니다.
      말씀 정말 감사하고요. 더 열심히 공부해보도록 하겠습니다!
      서로 좋은 의미로 파이팅할 수 있기를 바라봅니다 :-)

    • Favicon of https://myage.tistory.com BlogIcon 바다21 2020.05.06 13:08 신고

      파이팅이란... 저도 새로 알게 된것들.. 블로그로 포스팅하고 싶은데.. 게을러서 전혀 못하고 있거든요 ㅠㅠ 정아마추어님 블로그보고 반성 많이 하고 갑니다. 그런 파이팅이에요~! 파이팅!! ㅋㅋ

    • 네~ 오해 없었습니다!
      좋은 말씀 다시 한 번 감사드립니다^^

  • 럭키아빠 2020.05.22 10:10

    지나가다가 저랑 완전 같은 고민을 하셨고, 잘 전개하셔서 댓글남깁니다! 화이팅

  • 바지년 2020.08.24 01:14

    자기 전에 궁금해서 찾고 있었는데, 좋은 글이 있군요ㅎ 감사합니다.

  • Favicon of https://sysgongbu.tistory.com BlogIcon 소연쏘 2020.09.27 12:58 신고

    좋은 글 감사합니다!! 많은 도움이 되었어요

  • galid1 2020.10.16 15:57

    잘 정리된것 같습니다!
    제가 생각하는 핵심 내용 한가지가 더있는데 어떻게 생각하시는지 궁금합니다.
    싱글코어에서 다중프로그래밍의 경우 어차피 한순간에는 하나의 프로세스(스레드)만 CPU를 할당받기 때문에, 수백 수천개의 요청이 와도 결국 한순간에는 하나의 스레드가 동작하기 때문에, 하나의 컨트롤러로 모든 요청처리가 가능하다고 생각합니다.
    이때 물론 정리해주신 것 처럼, 공유하는 데이터 즉, 클래스변수, 전역변수를 컨트롤러에서 사용하지 않기 때문에, "문제"가 없는 것이 아닌가 싶습니다.

    정리하자면, 한순간에 하나의 스레드만이 동작하므로, 하나의 컨트롤러로 모든 요청 처리가 "가능"하며,
    공유하는 데이터가 없기 때문에 "문제"가 발생하지 않는것 이라고 생각해봤습니다.