저고데

[개발 지식] 싱글톤 패턴에 대해서 알아보자 본문

개발 지식

[개발 지식] 싱글톤 패턴에 대해서 알아보자

진철 2024. 4. 12. 22:50
728x90
반응형
들어가며

항상 사이드 프로젝트를 만들 때마다 궁금한 점이 하나 있었다. 지금은 나 혼자만 사용하고 테스트하는 서비스이지만, 만약에 수 많은 사람들이 서비스에 접속하면 어떻게 될까? MVC 패턴으로 서비스를 Controller, Service, Repository로 구성했을 때, 여러 개의 HTTP 프로토콜이 온다면 Controller와 같은 객체도 과연 요청 갯수만큼 생성될지 궁금했다. 이번 시간에는 해당 궁금증에 대한 답변과 그 이유에 대해서 이야기해보도록 하겠다.

 

결론은

Controller와 같은 객체도 요청 갯수만큼 생성될까? 정답은 NO!이다. 당연한 이야기이겠지만, 요청 갯수만큼 객체가 생성된다면 사용되는 자원의 양이 어마무시하게 많아질 것이다. 만약 그렇다면 Naver와 같이 대규모 트래픽이 밥 먹듯이 발생하는 포털 사이트의 경우, 서버가 수천 대는 필요할 것이다. 설령, 사용자가 서비스를 종료할 때, 생성된 객체를 삭제한다고 하더라도 생성과 삭제하는 과정이 상당히 무겁기 때문에 이는 비현실적이고 비효율적이다.

따라서, 조금 더 효율적으로 자원을 관리하고 사용하는 방법이 필요했다. 이를 위해서 하나의 Controller, Service, Repository만을 생성하는 방식을 탄생하였고 실제로 대부분의 서비스는 해당 방식을 사용한다. (MVC 패턴을 예로 들었기 때문에 세 개만 언급하였습니다.) 

 

이를 우린 "싱글톤 패턴"이라고 한다.

 

싱글톤 패턴이란

필자가 현재 Java를 공부하고 있기 때문에 Java를 기준으로 설명해보도록 하겠다. Java를 실행시키면 해당 코드는 JVM을 통해서 작동될 것이고 JVM에 의해서 객체가 생성될 것이다. 싱글톤 패턴이란 객체가 프로그램에서 단 하나만 생성되어 존재하는 패턴을 의미한다. 조금 더 간단하게 말하자면, 앞서 MVC 패턴에서 Controller, Service, Repository와 같은 객체는 Java 코드가 실행할 때, 단 한 번만 생성되는 것이다.

이러한 특징으로 인해 수 많은 요청이 들어와도 객체를 또 다시 생성하지 않고 기존의 객체를 사용하여 자원을 효율적으로 사용할 수 있다. (실제로 객체를 생성하는 비용은 매우 크다) 그리고 클라이언트로부터 요청이 많은 웹 어플리케이션의 특성상, 객체에 신경을 쓰지 않고 클라이언트의 요청에만 집중할 수 있다는 장점이 있다.

 

Java에서 싱글톤 패턴을 만드는 방법

1. static private 메서드 사용

싱글톤 패턴은 프로그램이 실행될 때만 딱 하나를 생성하고 그 이후에는 접근만 가능한 형태이다. 따라서, static private 생성자를 사용하면 동일하게 패턴을 구성할 수 있다. static 메서드는 Java 실행 시에 메모리의 static이라는 영역에 객체를 내부적으로 생성한다. 그러므로 실행 시에 하나만 생성할 수가 있는 것이고 private 메서드로 인해서 외부에서는 생성할 수 없기 때문에 우리가 원하는 싱글톤 패턴을 구현할 수가 있다. (static 메서드에 관해서는 추후에 따로 블로그에 작성할 계획이다.)

하지만, 해당 방법은 구현하는 코드 자체가 길어지기 때문에 복잡하다. 그리고 의존 관계를 인터페이스와 같이 추상화로 만들어지 않고 직접 생성된 객체인 구체 인스턴스에게 의존하기 때문에 좋은 객체 지향 프로그래밍의 원칙 중 하나인 DIP를 위반한다는 단점이 존재한다.

 

2. @Configuration 어노테이션 사용

이는 순수 Java가 아닌 Spring에서 사용하는 방법이다. 그리고 프레임워크를 사용하기 때문에 당연히 1번 방법의 한계를 보완해준다. Spring의 싱글톤 컨테이너는 싱글톤 패턴의 코드를 작성하지 않아도 객체 인스턴스를 싱글톤으로 관리해준다는 특징이 있다. 이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라고 하는데, 코드를 작성하지 않아도 생성되고 관리를 알아서 해준다는 큰 장점이 있다. 이를 구현하는 방법은 아주 간단한데, 싱글톤 패턴으로 생성되기 희망하는 클래스 위에 "@Configuration"어노테이션을 붙여주면 끝이다. 그렇다면 이 방법은 어떤 원리인 것일까?

실제 테스트 코드를 통해서 @Configuration이 붙은 Config 클래스를 호출하면, Config 클래스명에서 +"CGLIB"라는 단어가 붙은 클래스가 호출되는 것을 확인할 수가 있다. 이는 개발자가 스스로 작성하여 생성한 것이 아니다. Spring이 "CGLIB"라는 바이트 조작 라이브러리를 사용하여 Config 클래스를 상속받은 임의의 다른 클래스를 만든 것이다. (그리고 이를 Spring Bean으로 등록한 것이다) 이 CGLIB가 붙은 클래스가 단 하나만을 생성되어 싱글톤을 보장하는 것이다. 당연하게도 @Configuration을 제거하면 CGLIB가 붙지 않은 Config 클래스를 생성된다. 해당 클래스는 싱글톤 패턴이 없기 때문에 여러 번의 요청에 여러 번 객체가 생성된다.

 

싱글톤 패턴을 작성할 때의 주의사항

어떻게 보면 마법처럼 보이고 강력한 기능을 제공하지만, 잘못 사용하면 예기치 않은 문제가 발생할 수 있다. 그 중에서도 가장 중요한 주의사항은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하는 경우이다.

이게 무슨 말이냐. 은행 시스템을 예시를 들어보자. 사용자 A와 B가 순차적으로 저금을 한다고 가정해보자. A가 100원을, B가 200원을 순서대로 저금한다고 하면, A의 통장을 조회했을 때 100원이 나와야 한다. 그러나 싱글톤 패턴을 사용하여 객체의 상태가 유지되면 마지막에 B가 200원을 저금했기 때문에 조회 결과로 200원이 나올 수 있다. 이는 각 사용자가 같은 인스턴스에 접근하므로 발생한 문제로 실제로 은행 서비스에서 이와 같은 문제가 발생한다면 나라 전체가 뒤집어질만큼 아주 중대한 사항이다.

이를 방지하기 위해서는 상태를 유지하는 stateful이 아닌, 무상태인 stateless로 설계해야 한다. 싱글톤 객체의 상태가 클라이언트 간에 공유되기 때문에 상태를 변경하면 예기치 않은 결과가 발생할 수 있다. 따라서, 상태를 가지지 않는 stateless한 설계를 통해 이러한 문제를 방지할 수 있다.이 외에도 싱글톤 패턴은 다중 쓰레드 환경에서 안전하게 사용되어야 한다. 여러 사용자가 여러 쓰레드에서 동시에 싱글톤 객체에 접근할 경우, 경합 조건(race condition)이 발생할 수도 있다. 이는 데드락과 같은 예기치 않은 동작이 발생하여 시스템이 먹통이 될 수도 있기 때문에 이를 예방하기 위해서는 쓰레드 안전성을 보장해야 한다.마지막으로 강력한 결합을 피하기 위해 싱글톤 객체가 직접 다른 객체를 생성하거나 의존하지 않도록 해야 한다. 이를 위해선 의존성 주입(Dependency Injection)을 통해 외부에서 객체를 주입받도록 설계해야 한다.

 

마치며

오늘은 이렇게 싱글톤 패턴이 무엇인지, 그리고 왜 필요한지에 대해서 알아보았다. 평상시에 아무 생각 없이 접속하던 서비스들이 이러한 성격을 띄고 있다는 것에 놀라고 개발자들의 아이디어에 더 놀라지 않을 수 없었다. 더욱 놀란 점은 이러한 기술도 아주 오래되었다는 점인데, 더 노력해야할 듯하다.

728x90
반응형