Engineering Note

Consumer Group인데 Consumer가 아니다: Kafka 안에서 노드들이 서로를 찾는 법

Kafka Connect와 Schema Registry가 Kafka의 group coordination을 활용해 노드 멤버십, 리더 선출, 작업 분배를 다루는 방식을 정리합니다.

2026년 6월 17일 · Pletor Engineering kafkakafka-connectschema-registrycoordination

Kafka에서 consumer group이라고 하면 보통 이런 장면을 떠올립니다.

여러 consumer가 같은 topic을 나눠 읽는다.
partition은 consumer들에게 분배된다.
consumer lag으로 처리 지연을 본다.

대부분 맞습니다. 하지만 이것이 consumer group의 전부는 아닙니다.

Kafka에는 “메시지를 읽기 위한 consumer group”처럼 보이지 않지만, group coordination을 이용해 노드들이 서로를 발견하고 역할을 나누는 패턴이 있습니다. Kafka Connect worker들이 대표적인 예이고, Schema Registry도 Kafka를 이용해 primary 선출과 상태 저장을 조율합니다.

그래서 가끔 이상한 장면을 만납니다.

분명 Kafka 안에 어떤 group이 있다.
일반 consumer group처럼 lag을 볼 수는 없다.
Java client나 lag 중심 도구에서는 기대한 방식으로 안 보인다.
그런데 kadm 같은 낮은 수준의 admin client로 보면 group 정보가 보인다.

이 글은 그 장면을 이해하기 위한 글입니다.

여러 종이배가 중심의 다른 색 배 주위에서 조율되는 차분한 일러스트
Kafka의 group coordination은 메시지를 나눠 읽는 일뿐 아니라 노드들이 서로를 찾고 역할을 나누는 데도 쓰일 수 있습니다.

먼저 가져갈 결론

  • Kafka의 group coordination은 메시지 소비만을 위한 기능이 아닙니다.
  • Kafka Connect distributed worker들은 같은 group에 join해서 서로의 존재를 알고 task assignment를 나눕니다.
  • Schema Registry는 Kafka를 schema 저장소로 쓰고, primary 선출에도 Kafka 기반 coordination을 활용합니다.
  • 이런 group은 일반 consumer group처럼 offset commit과 lag을 중심으로 보이지 않을 수 있습니다.
  • low-level client에서 group protocol 정보를 보면 “consumer가 아닌 consumer group”의 존재를 확인할 수 있습니다.
  • 직접 만든 애플리케이션도 이 패턴을 응용할 수 있지만, membership, assignment, heartbeat, 관측성을 직접 책임져야 합니다.

핵심은 이름보다 역할입니다.

consumer group = record consumption + group coordination

우리가 보통 사용하는 consumer group은 이 둘이 함께 있는 형태입니다. 하지만 일부 시스템은 뒤쪽, 즉 group coordination에 더 관심이 있습니다.

Kafka Connect worker는 어떻게 서로를 찾나

Kafka Connect를 distributed mode로 실행하면 여러 worker가 하나의 Connect cluster처럼 동작합니다. 각 worker는 같은 group.id를 사용합니다.

이 group은 단순히 topic을 나눠 읽기 위한 group이 아닙니다. worker들이 같은 group에 들어오면 Kafka broker의 group coordinator가 멤버십을 관리합니다. worker가 추가되거나 빠지면 rebalance가 발생하고, leader worker가 connector와 task를 worker들에게 다시 배치합니다.

단순화하면 이렇습니다.

worker-1 joins group
worker-2 joins group
worker-3 joins group

Kafka group coordinator tracks membership
leader computes connector/task assignment
workers receive their assignments

Connect worker들이 서로를 직접 찾아다니는 것이 아닙니다. 각 worker는 Kafka의 group coordinator를 만납니다. 그리고 group coordinator를 통해 “지금 이 group에 누가 있는가”와 “새 assignment를 받아야 하는가”를 알게 됩니다.

Connect의 설정, offset, status는 별도 internal topic에 저장됩니다. group coordination은 worker membership과 task assignment를 움직이는 축이고, internal topic은 durable state를 저장하는 축입니다. 둘을 섞어 보면 안 됩니다.

Schema Registry는 무엇이 다른가

Schema Registry도 여러 인스턴스로 실행할 수 있습니다. 이때 모든 인스턴스가 아무렇게나 schema를 쓰면 곤란합니다. 같은 subject에 대해 호환성 검사와 ID 부여가 일관되어야 하기 때문입니다.

Schema Registry는 Kafka를 schema 저장소로 사용합니다. 일반적으로 schema 정보는 Kafka topic에 저장되고, 여러 인스턴스 중 primary가 쓰기 요청을 담당하는 구조로 동작합니다. primary 선출과 인스턴스 coordination에도 Kafka 기반 메커니즘이 사용됩니다.

여기서 주의할 점이 있습니다. Schema Registry가 Kafka를 사용한다고 해서 모든 인스턴스 간 통신이 Kafka만으로 끝난다는 뜻은 아닙니다. 인스턴스들은 HTTP endpoint도 갖고 있고, 배포 방식에 따라 listener나 advertised host 설정도 중요합니다.

그래서 이 글에서는 이렇게 구분하겠습니다.

시스템Kafka를 쓰는 방식
Kafka Connectworker membership과 task assignment에 group 사용
Schema Registryschema 저장, primary 선출, 상태 조율에 Kafka 활용

둘 다 Kafka를 “메시지 저장소” 이상으로 씁니다. 하지만 세부 구현과 책임은 다릅니다.

왜 일반 consumer group처럼 보이지 않을까

우리가 익숙한 consumer group 조회는 대개 이런 질문을 던집니다.

이 group은 어떤 topic partition을 읽는가?
어디까지 offset을 commit했는가?
lag은 얼마인가?

그런데 Connect worker group이나 leader election용 group은 이 질문에 잘 맞지 않습니다.

이들은 topic record를 나눠 읽기 위해 존재하는 group이 아닐 수 있습니다. offset commit이 없거나, lag으로 볼 만한 대상이 없을 수 있습니다. group protocol type도 일반 consumer group과 다를 수 있습니다.

그래서 lag 중심 도구나 high-level consumer 관점의 조회에서는 “없다”거나 “기대와 다르게 보인다”고 느낄 수 있습니다.

하지만 이것은 Kafka에 group이 없다는 뜻이 아닙니다. 더 낮은 수준에서 group metadata를 보면 이야기가 달라집니다.

group id
protocol type
protocol name
members
member metadata
assignment

kadm처럼 Kafka admin protocol을 더 직접적으로 다루는 client에서는 이런 정보를 확인할 수 있습니다. 반대로 Java의 일반 consumer API나 consumer lag을 전제로 한 도구에서는 이런 group을 자연스럽게 다루기 어렵습니다.

정리하면 이렇습니다.

안 보인다 = 존재하지 않는다

가 아닙니다.

안 보인다 = 내가 쓰는 조회 관점과 맞지 않는다

에 더 가깝습니다.

직접 만든 애플리케이션에도 쓸 수 있을까

쓸 수 있습니다. 다만 “consumer group을 만든다”기보다 Kafka group protocol을 coordination 용도로 사용한다고 보는 편이 정확합니다.

예를 들어 여러 노드로 구성된 애플리케이션이 있다고 해보겠습니다.

각 노드는 시작할 때 같은 group에 join합니다. join metadata에는 자기 endpoint, 처리 가능한 shard, version, capability 같은 정보를 넣을 수 있습니다. leader 역할을 맡은 member는 전체 member metadata를 보고 assignment를 계산합니다. 그 결과는 sync 단계에서 각 member에게 전달됩니다.

흐름은 대략 이렇습니다.

node starts
  -> JoinGroup with node metadata
  -> one member becomes group leader
  -> leader computes assignment
  -> SyncGroup distributes assignment
  -> each node runs its assigned work
  -> heartbeat keeps membership alive

노드가 죽으면 heartbeat가 끊기고 session timeout 이후 group에서 빠집니다. 새 노드가 들어오면 rebalance가 발생합니다. 이 구조를 잘 활용하면 별도 service discovery나 coordination store 없이도 “지금 살아 있는 노드 집합”과 “작업 분배”를 Kafka 위에서 다룰 수 있습니다.

언제 좋은 선택인가

이 패턴은 다음 조건에서 매력적입니다.

  • 이미 Kafka를 운영하고 있습니다.
  • 노드 membership과 assignment가 함께 필요합니다.
  • 작업 단위가 rebalance로 재분배되어도 됩니다.
  • assignment 결과를 각 노드가 명확히 받아야 합니다.
  • 별도 ZooKeeper, etcd, Consul을 추가하고 싶지 않습니다.

예를 들면 이런 곳에 어울립니다.

  • 여러 worker 중 하나가 특정 shard를 담당해야 하는 경우
  • 노드 capability에 따라 작업을 나누고 싶은 경우
  • leader가 전체 멤버 목록을 보고 assignment를 계산해야 하는 경우
  • Kafka 기반 시스템 안에서 가벼운 cluster membership이 필요한 경우

하지만 단순히 “노드 목록만 알고 싶다”면 너무 무거울 수 있습니다. 그런 경우에는 DNS, service discovery, heartbeat table, Redis, database row 같은 단순한 구조가 더 나을 수 있습니다.

직접 구현할 때 조심할 점

Kafka group protocol을 직접 쓰면 재미있습니다. 하지만 공짜는 아닙니다.

첫째, high-level consumer API만으로는 충분하지 않을 수 있습니다. 일반 consumer API는 record consumption을 중심으로 설계되어 있습니다. custom protocol metadata, assignment, low-level group handling을 다루려면 더 낮은 수준의 client가 필요할 수 있습니다. Java가 아닌 client library를 선택하는 이유도 여기에 있습니다.

둘째, assignment 정책은 직접 설계해야 합니다. Kafka가 “누가 살아 있는가”를 알려준다고 해서 “무엇을 누구에게 맡길지”까지 자동으로 정해주지는 않습니다.

셋째, rebalance는 정상 동작입니다. 노드 추가, 노드 장애, 네트워크 지연, rolling deploy 때 assignment가 바뀔 수 있습니다. 작업은 재시작 가능해야 하고, 중복 실행이나 중간 중단을 견딜 수 있어야 합니다.

넷째, 관측성을 따로 만들어야 합니다. 일반 consumer lag dashboard는 이런 group을 제대로 설명하지 못할 수 있습니다. 최소한 다음은 봐야 합니다.

  • 현재 group members
  • leader member
  • assignment version
  • rebalance count
  • last successful sync time
  • member heartbeat 상태
  • assignment별 처리 상태

다섯째, durable state를 어디에 둘지 정해야 합니다. group membership은 현재 살아 있는 노드를 알려주지만, 영속 상태 저장소가 아닙니다. 장기 상태, checkpoint, 작업 결과는 별도 topic이나 database에 저장해야 합니다.

consumer group이라는 이름에 속지 않기

Kafka를 오래 쓰다 보면 이름 때문에 생기는 오해가 있습니다. consumer group도 그렇습니다.

일반 애플리케이션에서는 consumer group이 topic partition을 나눠 읽는 단위입니다. 그래서 lag, committed offset, partition assignment가 먼저 떠오릅니다.

하지만 Kafka의 group coordination은 더 일반적인 문제를 풀 수 있습니다.

누가 살아 있는가?
누가 leader인가?
작업을 어떻게 나눌 것인가?
멤버가 바뀌면 어떻게 다시 나눌 것인가?

Kafka Connect와 Schema Registry는 이 관점을 이해하기 좋은 사례입니다. 그리고 같은 관점은 직접 만든 애플리케이션에도 적용할 수 있습니다.

단, 이것은 숨겨진 마법이 아닙니다. Kafka가 membership과 rebalance라는 단단한 뼈대를 제공할 뿐입니다. 그 위에서 어떤 metadata를 주고받을지, assignment를 어떻게 계산할지, 실패를 어떻게 견딜지는 애플리케이션이 정해야 합니다.

Konduo는 Kafka처럼 broker, topic, consumer group, coordination 상태를 함께 봐야 하는 운영 대상을 플러그인으로 연결합니다. 글에서 본 group coordination 관점은 실제 운영에서도 리소스 상태, 메트릭 근거, 알림 대응을 한 흐름에서 읽어야 한다는 문제로 이어집니다.

결론은 간단합니다.

모든 consumer group이 메시지를 소비하는 것은 아닙니다.
어떤 consumer group은 노드들이 서로를 찾고, 역할을 나누기 위해 존재합니다.

함께 읽기 좋은 글

이 글의 관점과 이어지는 주제를 더 보려면 아래 글도 함께 읽어볼 수 있습니다.