How routing keys isolate Kafka consumer tests on a shared broker

The New Stack · 2026.07.28
How routing keys isolate Kafka consumer tests on a shared broker

공유 브로커에서 Kafka 컨슈머 테스트가 어려운 이유

Kafka를 사용하는 서비스에서 컨슈머 로직을 변경했을 때, 그 변경이 실제로 올바르게 동작하는지 확인하려면 결국 실제 시스템에서 실행해봐야 한다는 문제가 있다. 로컬이나 CI 환경에서 공유 Kafka 브로커를 사용하는 경우, 테스트 간 메시지가 서로 간섭하거나 다른 컨슈머 그룹이 의도치 않게 메시지를 소비해버리는 상황이 빈번하게 발생한다.

특히 4년차 이상의 백엔드 개발자라면 이런 상황을 경험해봤을 것이다. 별도 브로커를 띄우는 방식(TestContainers 등)은 격리성은 확보되지만, 브로커 부트 타임이나 리소스 비용이 부담이 된다. 팀 전체가 하나의 공유 브로커를 사용하는 환경이라면 더욱 그렇다.

라우팅 키를 활용한 테스트 격리 전략

핵심 아이디어는 라우팅 키(routing key) 를 메시지에 포함시켜, 특정 테스트 실행에서 발행된 메시지가 해당 테스트의 컨슈머에게만 전달되도록 격리하는 것이다. 각 테스트 실행마다 고유한 키(예: UUID)를 생성하고, 프로듀서가 메시지를 발행할 때 이 키를 헤더나 페이로드에 포함시킨다. 컨슈머 쪽에서는 이 키를 확인해 자신이 처리해야 할 메시지인지를 필터링한다.

// 메시지 발행 시 라우팅 키 삽입
ProducerRecord<String, String> record = new ProducerRecord<>(TOPIC, payload);
record.headers().add("routing-key", testRoutingKey.getBytes());
producer.send(record);
// 컨슈머에서 라우팅 키 필터링
ConsumerRecord<String, String> record = ...; 
String routingKey = new String(record.headers().lastHeader("routing-key").value());
if (!routingKey.equals(expectedRoutingKey)) {
    return; // 다른 테스트의 메시지는 무시
}
processMessage(record);

이 방식은 토픽을 테스트마다 새로 만들 필요 없이, 동일한 토픽을 공유하면서도 논리적인 격리를 달성할 수 있게 해준다. Kafka의 파티션 구조나 컨슈머 그룹 설정을 바꾸지 않아도 된다는 점에서 인프라 부담이 적다.

실무 적용 시 고려할 점

이 접근 방식을 실제 프로젝트에 적용할 때는 몇 가지를 고려해야 한다.

  • 라우팅 키의 생명주기 관리: 테스트가 종료되면 해당 라우팅 키로 들어온 미처리 메시지가 남을 수 있다. 테스트 픽스처에서 명시적으로 정리하거나, 키에 타임스탬프를 포함해 오래된 메시지를 무시하는 전략이 필요하다.
  • 프로덕션 코드 오염 주의: 라우팅 키 필터링 로직이 프로덕션 컨슈머 코드에 직접 들어가면 관심사 분리가 깨진다. 테스트 전용 데코레이터나 인터셉터 레이어를 두어 프로덕션 코드와 분리하는 것이 바람직하다.
  • 컨슈머 그룹 격리 병행: 라우팅 키만으로 완전한 격리가 어렵다면, 테스트별로 고유한 컨슈머 그룹 ID를 사용하는 방식과 조합하면 격리 수준을 높일 수 있다.
  • 성능 영향 최소화: 필터링 로직은 가능한 컨슈머 초입에서 빠르게 처리해 불필요한 역직렬화나 비즈니스 로직 실행을 막아야 한다.

정리

  • 공유 Kafka 브로커 환경에서 라우팅 키를 메시지 헤더에 삽입하면, 별도 브로커 없이도 테스트 간 논리적 격리가 가능하다.
  • 라우팅 키 필터링 로직은 프로덕션 코드와 분리된 인터셉터 레이어에 위치시켜 코드 오염을 방지해야 한다.
  • 컨슈머 테스트의 격리성 확보는 실제 시스템 배포 전 변경 사항을 신뢰할 수 있게 검증하는 기반이 되며, 운영 안정성과 직결된다.
Source
The New Stack
원문 보기 →
← 목록으로 돌아가기