Netflix의 실시간 서비스 의존성 맵, 어떻게 설계했나
Netflix는 수천 개의 마이크로서비스가 운영되는 환경에서 서비스 간 의존 관계를 실시간으로 시각화하는 Service Topology 시스템의 스트리밍 파이프라인을 프로덕션 규모에 맞게 재설계한 경험을 공개했다. 단순한 의존성 그래프를 넘어, 실시간으로 변화하는 서비스 토폴로지를 안정적으로 추적하는 일은 대규모 분산 시스템에서 상당한 엔지니어링 도전과제다.
3단계 파이프라인으로 책임 분리
Netflix는 파이프라인을 중간자 해석(Intermediary Resolution) → 보강(Enrichment) → 영속화(Persistence) 세 단계로 명확히 분리했다. 각 단계는 독립적인 처리 책임을 가지며, 이를 통해 특정 단계의 장애나 지연이 전체 파이프라인에 연쇄적으로 전파되는 것을 방지한다.
이러한 관심사 분리(Separation of Concerns) 설계는 운영 관점에서도 중요하다. 단계별로 독립적인 스케일링과 모니터링이 가능해지고, 특정 처리 로직만 교체하거나 재배포할 수 있기 때문이다. 실무에서 모놀리식 스트리밍 파이프라인이 점점 비대해지다 결국 유지보수 불가능한 상태가 되는 패턴을 흔히 볼 수 있는데, Netflix의 접근 방식은 이를 사전에 방지하는 구조적 선택이다.
레코드 유실 대신 Kafka 백프레셔 전파
부하가 급증할 때 많은 시스템이 선택하는 쉬운 방법은 처리하지 못한 레코드를 그냥 버리는 것이다. Netflix는 이 방식을 택하지 않고, 백프레셔(Backpressure)를 Kafka 상류로 전파하는 방식을 선택했다. 즉, 하위 컨슈머가 처리 속도를 따라가지 못할 때 Kafka의 컨슈머 랙을 자연스럽게 증가시켜 상류에서 속도를 조절하도록 유도한다.
// 단순한 개념 예시: 처리 실패 시 재시도 큐로 넘기지 않고
// 컨슈머 오프셋 커밋을 보류하여 Kafka 백프레셔 유발
while (isRunning) {
ConsumerRecords<K, V> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<K, V> record : records) {
boolean success = process(record); // 처리 실패 시 커밋 안 함
if (success) consumer.commitSync();
}
}
서비스 의존성 데이터는 유실되면 토폴로지 맵의 정확성이 훼손되므로, 성능보다 정확성을 우선시한 설계 철학이 잘 드러난다. 실시간성이 중요한 시스템일수록 "빠르지만 부정확한 데이터"보다 "약간 느리지만 신뢰할 수 있는 데이터"가 훨씬 가치 있다.
gRPC 대신 SSE를 선택한 이유
내부 서비스 간 고용량 데이터 전송에 Netflix는 gRPC가 아닌 Server-Sent Events(SSE) 를 채택했다. gRPC는 양방향 스트리밍과 강한 타입 계약이 장점이지만, 단방향 고속 스트리밍 시나리오에서는 SSE가 더 가볍고 효율적이다. SSE는 HTTP/1.1 기반으로 동작하며 별도의 클라이언트 라이브러리 없이도 쉽게 연동 가능하고, 프록시나 로드밸런서와의 호환성도 뛰어나다.
이 선택은 "항상 최신 기술이 정답은 아니다"라는 실용적 관점을 보여준다. 프로토콜 선택은 데이터 흐름의 방향성, 페이로드 크기, 운영 복잡도를 종합적으로 고려해야 한다. 단방향 스트리밍이 명확한 요구사항이라면 SSE는 여전히 강력한 선택지다.
정리
- 스트리밍 파이프라인은 해석 → 보강 → 영속화처럼 책임을 단계별로 분리해야 독립적 스케일링과 유지보수가 용이하다
- 부하 급증 시 레코드를 유실하기보다 Kafka 백프레셔를 활용해 상류로 흐름을 제어하면 데이터 정합성을 지킬 수 있다
- 단방향 고속 스트리밍 내부 통신에서는 gRPC보다 SSE가 더 단순하고 효율적인 선택이 될 수 있다