-
[Kafka 101] 스키마 레지스트리 (Schema Registry)개발자 라이프/카프카 2020. 4. 26. 13:25반응형
들어가며
카프카는 메시지를 보내는 프로듀서와 메시지를 복사해오는 컨슈머, 그리고 프로듀서와 컨슈머 사이에서 메시지를 중계하는 브로커로 구성됩니다. 이러한 카프카의 구조는 메시지의 송신자와 수신자 사이의 직접적인 관계를 끊음으로써 구조적인 결합도를 낮추는 장점이 있습니다. 하지만, 반대로 직접적인 관계가 끊어짐에 따라 발생하는 이슈도 있습니다. 이번 글은 카프카에서 발생할 수 있는 운영 이슈와 그 이슈를 해결할 수 있는 스키마 레지스트리(Schema Registry)에 관하여 정리합니다.
스키마 레지스트리 (Schema Registry)
스키마 레지스트리는 카프카 클라이언트 사이에서 메시지의 스키마를 저장, 관리하는 웹 어플리케이션입니다. 그리고 스키마 레지스트리는 많은 카프카 개발자와 운영자들이 카프카 운영에 필수로 꼽습니다. 이처럼 스키마 레지스트리는 운영과 밀접한데, 그 이유에 대해서 먼저 살펴봅니다.
1. 등장 배경
카프카는 클라이언트 사이의 관계를 끊습니다. 즉,
- 프로듀서는 어떤 컨슈머가 메시지를 가져갈 지 모릅니다.
- 컨슈머는 어떤 프로듀서가 메시지를 보냈었는 지 모릅니다.
그리고 브로커는 메시지를 저장을 로그(Log)라는 자료구조 형태로 저장합니다. 이 로그 자료구조는 일반적으로 우리가 언급하는 시스템 로그, 애플리케이션 로그 등의 추상화된 자료구조입니다. 가장 큰 특징은 Append-Only인데, 다음과 같은 속성을 가집니다.
- 쓰기 작업은 가장 말단에서만 실행된다.
- 중간 수정 작업을 할 수 없다.
결국 브로커는 메시지를 한 번 저장하고 나면 이후에 수정할 수 없습니다. 이처럼 카프카의 구조적인 특징과 내부 구조로 인해, 카프카 운영에서 아래 그림과 같은 상황이 발생할 수 있습니다.
프로듀서 1번과 2번은 각자 토픽 A에 메시지를 보냅니다. 그리고 컨슈머는 그 토픽을 구독합니다. 이때, 2번 프로듀서가 스키마를 변경하여 메시지(4번)를 발행했습니다. 스키마 변경 사유는 로직 변경, 단순 오류 등 다양할 수 있습니다. 하지만 컨슈머는 이 상황을 알지 못하기 때문에, 4번 메시지를 구독하여 처리하는 과정에서 메시지를 읽어드리지 못하고 장애가 발생합니다.
컨슈머가 제대로 읽어드리지 못하는 이유는 프로듀서는 직렬화하여 메시지를 발행하고, 컨슈머는 역직렬화하여 메시지를 구독하기 때문입니다. 따라서 프로듀서와 컨슈머에 각각 메시지 구조(스키마)에 따라 직렬화/역직렬화 클래스가 구성되고, 이 둘은 강한 의존성(커플링, Coupling)을 갖게 됩니다. 결국, 구조적인 결합도는 낮췄지만 내부적인 결합도는 여전히 가지고 있게 됩니다.
위는 큰 사이즈의 메시지를 발행-소비하는 방법에 관한 예제 글에서 적용된 컨슈머 쪽 역직렬화 클래스입니다. 메시지 값에 해당하는 ImageChunk의 필드를 예상하고, 브로커에서 복사해온 바이트 배열을 역직렬화 합니다. 만약 바이트 배열이 역직렬화 로직과 맞지 않다면 정상적으로 처리할 수 없게 됩니다.
2. 목적과 기능
앞서 살펴본 것처럼 클라이언트 사이에는 여전히 메시지 구조에 대한 강한 결합도를 가지고 있습니다. 스키마 레지스트리는 이 결합도를 낮추기 위해 고안되었습니다.
스키마 레지스트리는 별도의 웹 어플리케이션 형태로 구성되며, 기능은 다음과 같습니다.
- 토픽 별 메시지 Key 또는 Value 스키마 버전 관리
- 스키마 호환성 규칙 강제
- 스키마 버전 조회
위 세 가지 기능들을 중 가장 핵심은 2번(스키마 호환성 규칙 강제)입니다. 운영자는 스키마를 등록하여 사용할 수 있지만, 스키마 버전 별 호환성을 강제함으로써 일종의 개발 운영 규칙을 세우는 것입니다. 스키마 호환성은 크게 Backward, Forward, Full, None 이 있습니다. 간단히 버전 1,2 스키마를 예를 들어 설명하면 다음과 같습니다.
- Backward :
- 컨슈머는 2번 스키마로 메시지를 처리하지만 1번 스키마도 처리할 수 있습니다
- 필드 삭제 혹은 기본 값이 있는 필드 추가인 경우
- Forward :
- 컨슈머는 1번 스키마로 메시지를 처리하지만 2번 스키마도 처리할 수 있습니다.
- 필드 추가 혹은 기본 값이 있는 필드 삭제
- Full :
- Backward와 Forward를 모두 가집니다.
- 기본 값이 있는 필드를 추가 혹은 삭제
- None :
- 스키마 호환성을 체크하지 않습니다.
위 그림은 Forward 호환성을 갖는 경우에 대한 예시입니다. 컨슈머는 버전 1로 메시지를 처리하고 있습니다. 그리고 'Gender'라는 필드가 버전 2에서 추가되었고, 컨슈머는 버전 2 스키마를 갖는 메시지를 구독하여 처리합니다. 이때, 컨슈머는 새로 추가된 필드를 제외하고, 기존 버전 1에 맞춰 메시지를 처리합니다.
이처럼 마법 같은 일이 일어날 수 있는 것은 KafkaAvroSerializer/KafkaAvroDeserializer (KafkaAvroSerde)가 있기 때문입니다. KafkaAvroSerde 는 스키마 레지스트리와 연동하여 메시지를 어떻게 직렬화/역직렬 화할지 결정합니다. 특히, KafkaAvroDeserializer는 메시지 간 스키마가 다르더라도 적절하게 맵핑시켜 주기 때문에, 컨슈머 측에서 스키마가 다르더라도 유연하게 처리할 수 있도록 합니다.
3. 개발과 운영 적용
스키마 레지스트리를 이용한 클라이언트 개발과 운영에는 다음과 같은 룰이 필요합니다.
- 클라이언트는 스키마 레지스트리로부터 스키마 명세를 배포받아 메시지를 처리할 것
이는 클라이언트에서 메시지에 대한 객체를 구성하여 로직을 처리할 때, 스키마 레지스트리에서 배포받은 명세에 따라 객체를 구성해야 하기 때문입니다. 일반적으로 사용되는 Avro 방식으로 예를 들면, 아래와 같은 명세를 Avro 플러그인을 통해 빌드하면 관련 객체가 생성되고 그 객체를 이용하여 메시지를 생성 혹은 처리해야 합니다.
스키마 레지스트리는 스키마 생성, 조회, 관리에 대한 HTTP API를 제공하고 있습니다. 클라이언트들은 개발 간 이 API를 이용하여 스키마를 배포받을 수 있습니다. API 상세는 Confluent 문서에서 확인할 수 있습니다.
마무리
카프카를 통해 흘러가는 메시지는 언제든지 그 구조가 변경될 수 있습니다. 스키마 레지스트리는 그 구조를 관리함으로써, 클라이언트 사이의 유연한 관계를 갖도록 합니다. 이는 카프카 개발과 운영에 중요한 부분이고, 특히 카프카의 규모가 커질수록 필수적입니다.
ps 1. 혹시 잘못되거나 부족한 부분은 댓글로 남겨주시길 바랍니다.
ps 2. 내용이 마음에 드셨다면 공감 버튼❤️을 눌러주세요!반응형