서블릿에서는 성능 이슈가 있다? 빈은 어떻게 처리되는가?

maketheworldwise·2022년 4월 11일
0


이 글의 목적?

현재 공부하고 있는 내용과 이전에 공부했던 내용을 퍼즐을 맞추듯이 연결하는 과정에서 나름대로 성장을 하고 있다는 뿌듯함도 있었지만, 잘 연결이 되지 않다보니 잘못 공부를 한건가? 라는 의심이 갔다. 어떤 부분에서 막혔는지, 그리고 그 의문에 대한 해답을 정리해보자.

문제의 시발(?)점

문제는 서블릿과 관련하여 공부를 할 때 발견한 두 개념에서 시작되었다.

  • 각 요청당 쓰레드가 생성이 된다.
  • 서블릿은 싱글톤으로 관리된다.

그리고 나는 이 두 개의 개념으로 내 자신에게 계속 질문해가면서 깊게 생각해보았다.

성능적인 문제가 있지 않을까?

가장 먼저 생각이 났던 것은 성능적인 이슈였다.

문제가 되는 개념의 예를 들어보자. 100개의 동일한 URL(/hello) 요청이 들어온다면, 요청당 쓰레드가 생성이 되니 100개의 쓰레드가 생성이 될 것이고, 서블릿은 싱글톤으로 관리되니 - 결국 100개의 쓰레드 요청을 하나의 서블릿에서 처리하게 된다. 그럼 서블릿에 부담이 커져 데드락이나 동시성 같은 문제가 발생하지 않을까?

설계의 문제

서블릿 싱글톤에 상태 저장 로직이 들어가 있다는 것은 설계가 잘못되었다. 비록 서블릿이 힙 영역에 올라가면 공유되는 자원이지만, 결국 내부를 수정하는게 없기 때문에 아무런 문제가 없다. 즉, 읽기만하면 되니 성능상으로 문제가 없다.

(📌 . '설계가 잘못되었다'라는 문장에서 싱글톤은 절대로 상태 저장 로직이 들어가 있으면 안된다고 생각할 수 있다. 하지만 절대라는 것은 아니다. 싱글톤 패턴에 상태 저장 로직이 들어가있으면 안된다는 이야기는 권장하는 것일 뿐, 무조건 안된다는 의미가 아니다.)

데드락 문제

데드락은 레이스 컨디션을 방지하고자 임계 영역에 락을 걸었을 때 발생하는 문제다. 하지만 서블릿에서는 임계 영역이 필요없다. 그럼 왜 임계 영역이 필요없는가?

임계 영역은 공유 데이터에 접근하는 코드나 동작 부분을 의미한다. 그렇다는 것은 임계 영역은 힙 영역과 관련이 있다는 것이다. 하지만 서블릿의 경우 상태를 저장하는 내용이 없고 메서드 호출만하니 힙 영역을 기준으로 생각하는 것보다 스택 영역을 기준으로 생각하는 것이 맞다.

결국에는 앞에서 설명한 내용과 동일하다. 임계 영역이 필요없는 이유는 상태를 가지지 않기 때문이라는 것이다.

결론

즉, 함수가 상태를 가지고 있지 않다면, 여러개의 쓰레드가 하나의 함수에 접근해서 처리한다고하여 성능상으로는 문제가 있지 않다!

싱글톤인데 여러 쓰레드가 접근할 수 있다?

그 다음으로 생각이 났던 것은 싱글톤과 관련된 내용이였다.

분명 DCL과 같이 특별한 처리를 하지 않는 이상, 싱글톤 패턴은 쓰레드 세이프하지 않다고 공부했다. 근데 위에서 살펴보았듯이 여러 개의 쓰레드가 하나의 서블릿에 동시에 접근한다는 의미는 결국 쓰레드 세이프하다는 의미이지 않은가?

  • 싱글톤 == 쓰레드 세이프 하지 않음
  • 서블릿 == 싱글톤
  • 서블릿 == 쓰레드 세이프 하지 않음 (?)
  • 서블릿 == 여러개의 쓰레드가 접근함 == 쓰레드 세이프 (?)

이것은 완전히 바보같은 질문이다. 서블릿은 싱글톤이고 쓰레드 세이프하지 않다는 것은 만들 때의 이야기이고, 그 서블릿에 여러개의 쓰레드가 접근하는 것은 과정 - 즉, 사용 시점에서의 차이가 있다는 이야기다.

적절한 비유라고 볼 수는 없지만, 아이가 태어나는 것과 아이가 커가는 과정은 엄연히 다르지 않은가? 따라서 이 질문은 객체를 만들 때와 사용하는 과정에서의 차이를 고려하지 않았기 때문에 이 질문 자체가 잘못되었다는 것이다.

빈은 다른가? 빈은 어떻게 처리되는가?

빈도 기본적으로 싱글톤으로 생성이 되고, 쓰레드 세이프 할 수도 있고 하지 않을 수 있다. 그럼 여기서 쓰레드 세이프 하지 않은 빈을 기준으로 생각해보자.

빈은 인스턴스와 같은 개념으로 볼 수 있고 빈을 등록하는 행위는 결국 힙 메모리 영역에 생성이 되는 것이라고 할 수 있는데, 한 개의 빈이 하나의 쓰레드에서 사용되고 있다면 다른 쓰레드에서는 해당 빈에 접근하지 못하기 때문에 데드락 문제로 이어지지 않을까?

이 질문에 대한 해답도 굉장히 간단하다. 빈이 쓰레드 세이프하지 않아 락을 걸었다면 동시성 이슈에 대한 부분은 해결이 되지만 데드락 문제가 발생할 수 있고, 빈을 쓰레드 세이프하게 구성했다면 동시성과 데드락에 대한 부분이 해결이 되는 것처럼, 이 질문에 대한 답은 결국 내가 어떻게 코드를 구현했는지에 따라 문제가 될 수도 안될 수도 있다는 것이다.

(📌 . 내가 들은 바에 따르면 - 빈이 쓰레드 세이프하지 않다는 것은 버그라고도 취급할 정도이기에 빈을 쓰레드 세이프하게 구현을 하는 것이 가장 옳은 방법이라고 한다.)

GC가 빈을 처리하지 않을까?

빈은 등록하면 힙 메모리 영역에 생성이 된다고 했다. 그럼 GC가 발생했을 때는 어떻게 되는가? 빈을 등록해도 그 빈을 바로 사용한다는 보장이 없으며, 사용하지 않을 때 GC가 동작하여 사용하지 않는 객체를 처리하면, 나중에 그 빈을 사용하려고해도 없어져서 문제가 발생하지 않을까?

이 부분은 GC가 처리하는 조건들을 살펴보면 쉽게 이해할 수 있다. GC는 해당 객체를 참조하고 있는 Root Set이 없을 경우에 처리를 하는데, 빈은 스프링 코드가 참조하고 있기 때문에 GC의 대상이 아니라서 처리가 되지 않는다. 그 대신 빈의 메서드를 호출로 인해 생성된 객체는 메서드가 종료되었을 때 GC의 대상이 된다.

이 글의 레퍼런스

profile
세상을 현명하게 이끌어갈 나의 성장 일기 📓

0개의 댓글