Engineering Note

Confluent 클라이언트로 Apicurio를 쓸 수 있을까: 스키마 호환성과 이벤트 연동

Confluent와 Apicurio의 Avro 직렬화·역직렬화 라이브러리 연결 방법, 메시지 형식과 스키마 ID 마이그레이션의 경계를 Java·Avro 예제로 설명합니다.

발행 · Pletor Engineering kafkaapicurioschema-registryavrojava

Kafka 클라이언트에서 Confluent Avro Serializer(KafkaAvroSerializer)를 사용한다고 해서 스키마를 저장하는 서버까지 반드시 Confluent Schema Registry여야 하는 것은 아닙니다. Apicurio Registry의 Confluent 호환 API를 사용하면 기존 클라이언트 라이브러리를 유지하면서 스키마 저장소를 연결할 수 있습니다.

다만 새 이벤트를 보내고 읽는 것과 기존 Registry를 교체하는 것은 다른 작업입니다. API 호출이 성공해도 과거 메시지에 기록된 스키마 ID를 찾지 못하면 소비는 실패합니다. 이 글에서는 Java와 Avro 예제로 연결 방법을 설명하고, API·메시지 형식·기능 호환성을 각각 확인하는 기준을 정리합니다.

서로 맞물린 테라코타·아이보리·세이지 색의 종이 구조물

기존 클라이언트를 유지할지 먼저 정한다

두 경로는 같은 Apicurio Registry 서버에 연결하지만, 사용하는 라이브러리와 API가 다릅니다.

상황검토할 연동 방식
기존 Confluent Avro Serializer·Deserializer를 유지하고 싶다Confluent 호환 API에 연결합니다.
Apicurio의 group·artifact 조회·등록 설정을 사용하고 싶다Apicurio 네이티브 Avro 라이브러리를 사용합니다.

기존 Confluent Avro 클라이언트를 연결하는 것이 목적이라면 먼저 호환 API로 필요한 동작을 확인하는 편이 변경 범위가 작습니다. 새 애플리케이션이라고 해서 반드시 네이티브 방식을 선택할 필요는 없습니다. 사용하는 언어와 라이브러리, 주변 도구의 지원 범위를 함께 판단해야 합니다.

Kafka 연결과 스키마 조회는 별도 경로다

Producer는 Kafka에 이벤트를 보내고 Consumer는 Kafka에서 이벤트를 읽습니다. 이 글의 Avro 예제에서는 Confluent Avro Serializer가 별도의 HTTP 경로로 Registry에 스키마를 등록하거나 조회하고, Confluent Avro Deserializer(KafkaAvroDeserializer)가 메시지에 기록된 ID로 스키마를 조회합니다. Registry가 이벤트 전달 경로 중간에 들어가는 구조는 아닙니다.

이벤트는 Kafka를 통해 전달되고, Avro Serializer와 Deserializer는 별도 HTTP 경로로 스키마를 조회합니다.
이벤트는 Kafka를 통해 전달되고, Avro Serializer와 Deserializer는 별도 HTTP 경로로 스키마를 조회합니다.

스키마 등록·조회 결과는 클라이언트에 캐시될 수 있습니다. 매 메시지마다 Registry를 호출하지는 않지만, 새 스키마나 캐시에 없는 ID를 처리할 때는 Registry 접근이 필요합니다. 캐시는 Registry 장애를 완전히 숨겨 주지 않습니다. Confluent 클라이언트 동작 설명

연결 설정은 어떻게 달라질까?

아래 설정은 다운로드 예제에서 사용하는 값입니다. kafka:9092와 registry:8080은 Docker Compose 내부 주소이며, 호스트에서 직접 접속하는 주소가 아닙니다. 두 방식 모두 key에는 Kafka의 StringSerializer·StringDeserializer를 사용하고 value를 Avro로 처리합니다.

Confluent Avro 라이브러리: 호환 API에 연결

io.confluent:kafka-avro-serializer를 사용합니다. Producer와 Consumer 모두 schema.registry.url에 호환 API 주소를 지정합니다.

Producer의 주요 설정:

bootstrap.servers=kafka:9092
schema.registry.url=http://registry:8080/apis/ccompat/v7
value.serializer=io.confluent.kafka.serializers.KafkaAvroSerializer
auto.register.schemas=true

Consumer의 주요 설정:

bootstrap.servers=kafka:9092
schema.registry.url=http://registry:8080/apis/ccompat/v7
value.deserializer=io.confluent.kafka.serializers.KafkaAvroDeserializer
specific.avro.reader=false

Confluent Schema Registry에서 subject는 스키마 버전과 호환성 정책을 관리하는 이름입니다. 기본 TopicNameStrategy는 토픽명에 -key 또는 -value를 붙입니다. 예제의 orders-confluent 토픽에서는 value 스키마가 orders-confluent-value subject에 등록됩니다.

Producer의 Avro Serializer는 스키마를 등록하거나 조회해 ID를 메시지에 기록합니다. Consumer의 Avro Deserializer는 그 ID로 데이터를 쓸 때 사용한 스키마(writer schema)를 찾아 GenericRecord로 읽습니다. Confluent Avro 라이브러리 문서

Apicurio Avro 라이브러리: 네이티브 API에 연결

io.apicurio:apicurio-registry-avro-serde-kafka를 사용합니다. 네이티브 API 주소는 apicurio.registry.url에 지정하며, 조회·등록 설정도 Apicurio의 키를 사용합니다.

Producer의 주요 설정:

bootstrap.servers=kafka:9092
apicurio.registry.url=http://registry:8080/apis/registry/v3
value.serializer=io.apicurio.registry.serde.avro.AvroKafkaSerializer
apicurio.registry.auto-register=true
apicurio.registry.artifact.group-id=default
apicurio.registry.artifact.artifact-id=orders-apicurio-value

Consumer의 주요 설정:

bootstrap.servers=kafka:9092
apicurio.registry.url=http://registry:8080/apis/registry/v3
value.deserializer=io.apicurio.registry.serde.avro.AvroKafkaDeserializer

Apicurio는 스키마를 group 안의 artifact로 관리합니다. 예제는 orders-apicurio 토픽의 스키마를 default group의 orders-apicurio-value artifact로 명시합니다. Consumer는 메시지의 ID로 스키마를 조회하므로 Producer의 자동 등록 설정을 복사할 필요가 없습니다. Apicurio 3.3.x 설정 문서

두 방식은 설정 키와 스키마 선택 방식이 다릅니다. 라이브러리를 바꾸면서 schema.registry.url이나 auto.register.schemas만 그대로 넘기는 방식으로는 연동되지 않습니다.

실제로 확인한 동작과 범위

구성 요소확인에 사용한 버전
Kafka 서버 / Java 클라이언트4.2.0 / 3.9.0
Apicurio Registry3.3.3 (pletorco/apicurio-registry:3.3.3-pletor.2)
Confluent / Apicurio Avro 라이브러리7.9.0 / 3.3.3
Avro / Java / Maven1.12.0 / 21 / 3.9.9

각 방식의 Producer·Consumer가 고유한 주문 ID와 금액을 보내고 같은 값을 읽는지 확인했습니다. 실행마다 새 클라이언트 프로세스를 사용해 이전 스키마 캐시에 의존하지 않도록 했습니다.

확인 항목결과
Confluent Avro 라이브러리의 이벤트 왕복주문 ID·금액 일치, 정상 종료
Apicurio Avro 라이브러리의 이벤트 왕복주문 ID·금액 일치, 정상 종료
호환 API에서 스키마 ID로 조회HTTP 200
BACKWARD 정책에서 기본값이 있는 필드 추가호환으로 판정
amount를 long에서 string으로 변경비호환 판정, 등록 시 HTTP 409

전체 코드, 고정 버전의 Maven 설정, 실행·정리 명령은 실행 예제 ZIP에 모았습니다. Docker Compose로 두 방식을 각각 실행하면 ROUND_TRIP_OK와 이벤트 값이 출력됩니다. 첫 실행에는 의존성을 내려받는 시간이 필요합니다.

Registry는 임시 H2 저장소를 사용하고 인증·TLS는 끈 구성입니다. 이번 확인은 각 방식에서 같은 공급자의 라이브러리를 양쪽에 사용한 Avro 연동입니다. 라이브러리 혼용, 기존 Registry 데이터 이전, KafkaSQL 내구성이나 Operator 배포까지 검증한 결과는 아닙니다. Kafka 서버와 클라이언트 버전이 같아야 한다는 의미도 아닙니다.

호환성은 세 가지로 확인한다

이벤트 왕복이 성공했다고 해서 모든 Confluent 기능을 대체할 수 있다는 뜻은 아닙니다. 호환성은 다음 세 영역으로 나누어 확인해야 합니다.

경계확인할 질문
REST API클라이언트가 사용하는 등록·조회·호환성 검사 API를 지원하는가?
메시지 형식식별자의 위치·크기·의미와 payload 형식이 일치하는가?
기능과 정책인증, 스키마 참조, subject 전략과 사용 중인 부가 기능을 지원하는가?

Apicurio의 3.3.x 호환 API 문서는 Confluent 클라이언트용 엔드포인트와 지원 범위를 설명합니다. 이 글의 예제는 /apis/ccompat/v7을 사용합니다. 실제 배포에서는 서버 패치 버전과 클라이언트 버전을 고정하고 필요한 API를 직접 확인해야 합니다.

Schema Linking/Exporters, KEK/DEK API, Confluent Data Contracts 규칙 실행까지 같은 방식으로 대체되는 것은 아닙니다. 특히 API가 metadata나 ruleSet을 받아들인다는 사실과 규칙을 실제로 실행한다는 사실은 구분해야 합니다. 스키마 진화 호환성과 Registry 제품 간 호환성도 서로 다른 개념입니다.

같은 Avro여도 바이트 형식은 다를 수 있다

전통적인 Confluent Avro 형식은 다음과 같습니다.

[0: 1바이트][스키마 ID: 4바이트, big-endian][Avro 데이터]

Avro 데이터 부분이 같아도 앞부분의 ID 처리 방식이 다르면 읽을 수 없습니다. Protobuf는 메시지 인덱스도 포함합니다. 최신 Confluent 클라이언트에는 헤더의 GUID를 사용하는 형식도 있으므로, 이 글의 payload-prefix 예제를 모든 버전·형식에 일반화하면 안 됩니다. Confluent 메시지 형식 문서

기존 Registry에서 ID 42였던 스키마를 새 Registry에 등록했을 때 ID 107이 나왔다고 해 봅시다. 과거 메시지에는 여전히 42가 남아 있습니다. URL을 바꾼 Consumer는 새 Registry의 42를 조회하므로, 스키마를 찾지 못하거나 다른 스키마를 읽을 수 있습니다. 마이그레이션은 ID와 스키마의 대응, 참조, 버전 이력까지 검증해야 합니다.

전환 시험에서는 최신 이벤트만 확인하지 말고 가장 오래된 보존 메시지, 여러 버전의 이벤트, DLQ와 재처리 토픽을 읽어 보세요. 기존 Consumer를 재시작해 캐시가 비어 있는 상태에서도 성공해야 합니다.

자동 등록과 스키마 진화 정책

실행 예제는 스키마 등록부터 이벤트 생산·소비까지 확인할 수 있도록 자동 등록을 켰습니다. 운영에서 배포 시점의 계약을 통제하려면 CI/CD에서 스키마를 사전 등록하고 Producer의 자동 등록을 끄는 방식을 검토할 수 있습니다.

Confluent Avro Serializer에서 자동 등록을 끄는 설정은 다음과 같습니다.

auto.register.schemas=false

이 설정은 스키마를 만들어 주지 않습니다. 해당 subject에 Producer가 조회할 스키마가 이미 있어야 합니다. 기본 설정에서는 직렬화할 레코드의 스키마로 조회하므로, 임의로 최신 버전을 선택하는 설정은 아닙니다. 또한 subject 명명 전략과 호환성 정책을 명시적으로 맞춰야 합니다.

Avro에서 기본값이 있는 필드를 추가하는 경우와 필드 타입을 바꾸는 경우는 호환성 결과가 다릅니다. BACKWARD는 새 reader schema가 이전 writer schema로 기록한 데이터를 읽는 방향입니다. 여러 과거 버전까지 읽어야 한다면 transitive 정책도 검토해야 합니다. 등록 단계의 스키마 검사가 금액의 음수 여부 같은 업무 규칙이나 Consumer 코드의 정상 동작까지 보장하지는 않습니다.

운영에서는 Registry의 상태도 관찰한다

Apicurio Registry에는 별도의 Operator가 있습니다. KafkaSQL 저장소를 선택하면 Kafka 클러스터에 연결하고, PostgreSQL 저장소를 선택하면 DB에 연결합니다. Registry의 저장소 연결과 애플리케이션의 스키마 조회는 별도 설정이며, Confluent 호환 API는 특정 Kafka 운영 도구를 요구하지 않습니다. Apicurio Operator 배포 문서

Pletor에서는 Registry의 실행 환경을 함께 관찰하기 위해 pletorco/apicurio-registry 이미지를 개발·배포하고 있습니다. 공식 Apicurio Registry 애플리케이션 이미지를 기반으로 Pletor node metrics agent와 JMX Exporter를 추가했습니다. 프로세스·cgroup·파일시스템 신호를 포트 9404의 /metrics 경로에서 Prometheus 메트릭으로 제공하며, 기존 Registry Operator와 UI를 사용합니다. 별도의 Prometheus 수집 설정은 필요합니다. 이 확장은 관측성을 보완하며, 클라이언트 호환성은 기반 Apicurio 버전과 직렬화·역직렬화 설정을 기준으로 확인합니다.

이런 신호는 Registry 오류와 실행 환경의 자원 압력을 함께 해석하는 데 도움이 됩니다. Konduo는 인프라 상태와 메트릭·로그 근거, 알림 대응을 하나의 운영 흐름으로 연결하는 플랫폼입니다. Registry 요청 오류와 Kafka 상태를 함께 살펴보면 클라이언트 연결 문제와 저장소 문제를 좁히기 쉬워집니다.

연결 완료를 판단하는 기준

  • 서버와 직렬화·역직렬화 라이브러리 버전, 네이티브·호환 API 주소를 기록했는가?
  • 새 이벤트뿐 아니라 과거 이벤트도 캐시 없이 읽을 수 있는가?
  • subject 전략, 스키마 ID 대응과 참조를 확인했는가?
  • 호환성 정책과 자동 등록 정책을 명시했는가?
  • Kafka와 Registry의 인증·TLS를 각각 확인했는가?
  • 사용 중인 Confluent 부가 기능의 지원 여부를 확인했는가?
  • Registry 장애, 스키마 조회 실패, 역직렬화 오류의 처리 경로가 있는가?

함께 읽기 좋은 글