Search

Schema Registry 101 - 3

그 중 다음 두 강의에 대한 내용이다. 강의 끝의 핸즈온도 설명한다:
Understanding Schema Subjects
Testing Schema Compatibility

Understanding Schema Subjects

스키마 subject(제목)은 스키마 레지스트리에서 스키마를 구분하기 위한 네임스페이스이다. 스키마 제목 단위로 호환성 검사를 하고 버전 번호와 연결한다.
몇 가지 네이밍 방식에 따른 전략이 있다:
TopicNameStrategy (기본 설정)
RecordNameStrategy
TopicRecordNameStrategy

TopicNameStrategy

기본 값인 네이밍 전략이고, 스키마 직렬화하는 대상에 따라 <토픽>-key 또는 <토픽>-value 이다(키에 스키마를 사용하는 경우가 흔치 않을거 같다). 토픽마다 스키마 subject를 강제하는 방식이다. 따라서 다른 스키마 subject를 등록하려하면 호환성 검사를 하지 않는다.

RecordNameStrategy

레코드 단위로 적용되는 네이밍 전략이다. 따라서 한 토픽 내에서 레코드마다 다른 스키마를 사용할 수 있다. <레코드.클래스.FQN>-key 또는 <레코드.클래스.FQN>-value 로 네이밍한다.
이 방법을 쓰면 클러스터 전체에서 특정 스키마의 버전이 강제된다. 어떤 토픽에서 어떤 버전의 스키마를 쓰는지 알 수 없기 때문이다.

TopicRecordNameStrategy

위의 두 전략의 조합으로써 <토픽>-<레코드.클래스.FQN>-key 또는 <토픽>-<레코드.클래스.FQN>-value 로 네이밍한다. 토픽마다 스키마 버전을 달리 할 수 있다. 스키마 버전은 subject 단위로 진화하고 호환성을 가지기 때문이다.

subject 이름 전략 사용

카프카 클라이언트에서 key.subject.name.strategy 또는 value.subject.name.strategy 에 기본 값인 io.confluent.kafka.serializers.subject.TopicNameStrategy 대신 다음을 사용할 수 있다:
io.confluent.kafka.serializers.subject.RecordNameStrategy
io.confluent.kafka.serializers.subject.TopicRecordNameStrategy

Testing Schema Compatibility

강의 첫 모듈에서 이야기한 것처럼, 기본적으로 컨슈머는 프로듀서가 자신이 처리할 수 있는 스키마로 레코드를 만들 것이라고 기대한다. 스키마 레지스트리가 없을 때의 암묵적인 계약이다.
만약 프로듀서에서 만드는 레코드의 스키마가 바뀌었다면? 위 그림의 예제에선 UUID라는 필드를 제거했다. 하지만 컨슈머는 여전히 UUID를 처리하려고 한다 .

스키마 호환성

스키마 레지스트리를 사용하면 이런 다른 스키마(버전)에 대한 호환성 검사를 할 수 있다. 호환성 검사는 이러한 스키마의 변경에도 클라이언트가 계속 동작할 수 있도록 스키마 갱신 시 가드레일 역할을 한다.
호환성 타입
스키마 resolution
BACKWARD
컨슈머에서 새 버전 스키마를 사용하지만 과거 버전 스키마도 처리할 수 있음
FORWARd
컨슈머에서 과거 버전 스키마를 사용하지만 새 버전 스키마도 처리할 수 있음
FULL
BACKWARD와 FORWARD 모두 커버함
NONE
호환성 검사를 하지 않음
BACKWARD_TRANSITIVE
컨슈머에서 새 버전 스키마를 사용하지만 “모든” 과거 버전 스키마도 처리할 수 있음
FORWARD_TRANSITIVE
컨슈머에서 과거 버전 스키마를 사용하지만 그 이후 “모든” 새 버전 스키마도 처리할 수 있음
FULL_TRANSITIVE
모든 버전에 대해 호환성이 있음
일반적인 버전 호환성은 한 버전 차이에 대한 것이지만, transitive가 있는 경우 모든 버전에 대해 있어야 한다.

하위 호환성 예시

필드 삭제의 경우(field_A) 무시한다.
필드 추가의 경우(NewField) 기본 값을 사용한다. 따라서 기본 값이 있는 필드를 새로 추가해야 하위 호환성이 있다.

상위 호환성 예시

필드 삭제의 경우(NewField_A) 기본 값을 따른다. 따라서 기본 값이 있는 필드를 삭제할 수 있다.
필드 추가의 경우(AnotherNewField) 무시한다.

완전 호환성 예시

위의 상/하위 호환성의 예시를 생각해보면, 기본 값이 있는 필드의 추가/삭제 시 완전 호환성이 보장된다. 하위 호환성이란 새 버전의 컨슈머가 과거 버전의 (스키마의) 프로듀서가 만든 레코드를 처리할 수 있다는 것이고, 상위 호환성은 아직 갱신 안된 과거 버전의 컨슈머가 새 버전의 프로듀서가 만든 레코드를 처리할 수 있다는 것이다. 이 두 조합이 자유롭게 처리될 때 완전 호환성이 있다.

스키마 호환성 설정과 검증

스키마 레지스트리 subject 등록/갱신과 마찬가지로 세가지 방법이 있다. CLI confluent , REST API, gradle 플러그인. 실습은 CLI confluent 를 사용해서 간단하게 해본다:
# CLI `confluent` $ confluent schema-registry subject update <subject> \ --compatibility=<mode> $ confluent schema-registry compatibility validate \ --schema <schema-path> \ --subject <subject> \ --version <version> \ --type <schema-format>
Shell
복사
# REST API $ curl -X PUT -H "Content-Type: application/vnd.schemaregistry.v1+json" \ --data '{"compatibility": "<mode>"}' \ <sr-url>/config/<subject> $ jq '. | {schema: tojson}' <schema-path> | \ curl -u <API_KEY:API_SECRET> \ -X POST <sr-url>/comaptibility/subjects/<subject>/versions/<version> \ -H "Content-Type:application/json" -d @-
Shell
복사
schemaRegistry { ... config { subject('<subject>', '<COMPATIBILITY>') } ... compatibility { subject('<subject>', '<subject/path>', '<SCHEMA-FORMAT>') } ... }
Groovy
복사
실습 프로젝트의 경우 config 구성은 하지 않아 기본 값인 BACKWARD, 하위 호환성으로 되어 있다:
이전 실습에서 만든 스키마의 필드는 모두 기본 값을 가지고 있다. 그리고 필드에 대한 기본 값도 자동으로 붙여버린다(특히 프로트버프의 경우 proto3에서 강제한다). 따라서 기본 값이 없는 새 필드 추가로 하위 호환성을 깨는 것이 어렵다.
호환성을 깨는 단순한 예시로 필드 타입을 바꾸는 방법이 있다:
syntax = "proto3"; package io.confluent.developer.proto; option java_outer_classname = "PurchaseProto"; message Purchase { string item = 1; double total_cost = 2; double customer_id = 3; }
Protobuf
복사
string이었던 customer_id 를 double로 바꾸고 (하위) 호환성 검사를 하면 실패한다:
❯ confluent schema-registry compatibility validate \ --schema src/main/proto/purchase.proto \ --subject proto-purchase-value \ --version latest \ --type PROTOBUF +------------+-------+ | Compatible | false | +------------+-------+
Shell
복사
따라서 이 스키마 변경은 등록되지 않을 것이다. 필드 타입 변경의 경우 한 버전에 호환성을 지키며 바꾸기 어려울 것이다. 새 타입의 새 필드를 추가하고 과거 필드는 삭제하며 새 필드의 이름을 과거 필드로 바꾸는 과정이 필요하다. 각각 호환성을 지키며 여러 버전에 걸쳐 갱신해야 한다.