"우리 회사는 Java, Node.js, Go를 다 씁니다. 그런데 로그 포맷을 변경하려니 3가지 언어의 로깅 라이브러리를 모두 수정하고 배포해야 하네요?"
마이크로서비스 환경에서는 모든 서비스가 인증, 로깅, 모니터링, 트레이싱 같은 공통 기능(횡단 관심사)을 가져야 합니다. 이걸 해결하기 위해 **"내 코드 안에 라이브러리를 넣는 방식"**과 **"내 컨테이너 옆에 도우미 프로세스를 띄우는 방식"**이 치열하게 경쟁합니다.
1. 방식 1: 라이브러리/SDK 방식 (Fat Client) - "코드 내장형"
전통적인 방식입니다. 공통 기능을 .jar나 npm package 형태의 라이브러리로 만들어서 각 마이크로서비스 프로젝트에 의존성으로 추가합니다. 넷플릭스 OSS(Eureka, Ribbon, Hystrix)가 이 방식을 사용했습니다.
특징
- 언어 종속적: Java용 SDK, Go용 SDK, Python용 SDK를 각각 개발하고 관리해야 합니다.
- 높은 성능: 프로세스 내부에서 함수 호출로 실행되므로 네트워크 오버헤드가 없습니다.
코드 예시 (Spring Cloud)
Java
// build.gradle (라이브러리를 직접 의존)
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix'
implementation 'org.springframework.cloud:spring-cloud-starter-sleuth' // 로깅/트레이싱
}
// 애플리케이션 코드
@SpringBootApplication
@EnableHystrix // 코드 레벨에서 기능 활성화
public class MyApplication {
// 비즈니스 로직과 인프라 로직이 한 프로젝트 안에 섞임
}
장단점
- 장점:
- 성능: 네트워크를 타지 않고 메모리 내부에서 동작하므로 가장 빠릅니다.
- 디버깅: IDE에서 브레이크 포인트를 찍고 한 번에 추적하기 쉽습니다.
- 단점:
- 폴리글랏(Polyglot) 지원 불가: 새로운 언어를 도입할 때마다 인프라 라이브러리를 새로 짜야 합니다.
- 의존성 지옥: 라이브러리 버전을 올리려면 수백 개의 마이크로서비스를 전부 재배포해야 합니다.
2. 방식 2: 사이드카 컨테이너 방식 (Sidecar Container) - "인프라 위임형"
쿠버네티스(K8s)와 서비스 메시(Istio, Envoy)가 주도하는 현대적인 방식입니다. 오토바이 옆에 보조석(Sidecar)을 달고 달리듯, 애플리케이션 컨테이너 옆에 보조 컨테이너를 하나 더 띄워서 통신과 관리를 전담시킵니다.
특징
- 언어 독립적: 애플리케이션이 Java든 Go든 상관없습니다. 사이드카는 네트워크 패킷만 가로채서 처리하니까요.
- 투명성: 애플리케이션은 사이드카의 존재를 모릅니다. 그냥 localhost로 통신할 뿐입니다.
설정 예시 (Kubernetes Pod)
YAML
apiVersion: v1
kind: Pod
metadata:
name: my-service-pod
spec:
containers:
# 1. 메인 애플리케이션 (비즈니스 로직만 집중)
- name: main-app
image: my-java-app:v1
ports:
- containerPort: 8080
# 2. 사이드카 (Envoy Proxy - 로깅, 인증, 서킷브레이커 담당)
- name: sidecar-proxy
image: envoyproxy/envoy:v1.18
args: ["-c", "/etc/envoy.yaml"]
# 메인 앱으로 들어오고 나가는 모든 트래픽을 가로채서 처리함
장단점
- 장점:
- 관리의 분리: 인프라팀이 사이드카 설정만 바꾸면, 개발팀의 코드 수정 없이 보안 정책이나 로그 포맷을 일괄 변경할 수 있습니다.
- 다양한 언어 수용: 어떤 언어로 개발하든 똑같은 사이드카(Envoy 등)를 붙이면 표준화된 관리가 가능합니다.
- 단점:
- 네트워크 오버헤드: 서비스 간 통신 시 App A -> Sidecar A -> Sidecar B -> App B 경로를 타야 하므로 미세한 지연(Latency)이 발생합니다.
- 리소스 사용량: 모든 파드마다 사이드카 컨테이너가 하나씩 더 뜨기 때문에 메모리와 CPU를 더 많이 먹습니다.
3. 실무 비교: 언제 무엇을 쓰는가?
| 구분 | Library / SDK (Fat Client) | Sidecar Container (Service Mesh) |
| 위치 | 애플리케이션 프로세스 내부 | 같은 파드(Pod) 내 별도 컨테이너 |
| 언어 지원 | 언어별로 각각 개발 필요 | 모든 언어 통합 지원 |
| 업데이트 | 앱 코드 수정 및 재배포 필수 | 사이드카 이미지만 교체 가능 |
| 성능 | 최상 (In-process) | 약간 저하 (Network Hop 발생) |
| 복잡도 | 의존성 관리가 복잡함 | **인프라 설정(K8s)**이 복잡함 |
| 추천 상황 | 단일 언어(Java only), 극한의 성능 요구 | 다양한 언어(Polyglot), 대규모 MSA, Istio 도입 |
결론
팀이 **"우리는 죽어도 Java와 Spring만 쓴다"**라고 한다면 라이브러리(SDK) 방식이 성능 면에서 유리하고 관리도 익숙할 것입니다.
하지만 **"Node.js도 쓰고 Python도 쓰고 Go도 쓴다"**거나 **"인프라 로직과 비즈니스 로직을 완벽하게 분리하고 싶다"**면, **사이드카 패턴(Service Mesh)**을 도입하는 것이 운영의 지옥에서 탈출하는 유일한 방법입니다.
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
| 리더 선출 패턴(Leader Election Pattern) 완벽 정리 (0) | 2025.12.07 |
|---|---|
| 비동기 요청-응답 패턴(Asynchronous Request-Reply) 완벽 정리 (0) | 2025.12.07 |
| 벌크헤드 패턴(Bulkhead Pattern) 완벽 정리 (0) | 2025.12.07 |
| 서비스 디스커버리 패턴(Service Discovery) 완벽 정리 (0) | 2025.12.07 |
| 재시도 패턴(Retry Pattern) 완벽 정리 (0) | 2025.12.07 |