Kafka와 CDC를 왜 같이 쓰는가
📘 OEA
Kafka가 뭔지, 왜 쓰는지
서버 두 개가 통신해야 할 때 가장 먼저 떠오르는 방식은 직접 호출입니다.
A 서비스가 B 서비스를 HTTP로 바로 부르면 됩니다.
처음에는 이 방식이 가장 단순합니다.
하지만 트래픽이 늘고 장애 상황이 들어오기 시작하면 단점이 금방 드러납니다.
- B가 잠깐 죽어 있으면 A도 실패합니다.
- B가 느리면 A도 같이 느려집니다.
- A가 갑자기 요청을 몰아 보내면 B는 버티기 어렵습니다.
이럴 때 많이 등장하는 게 메시지 브로커입니다.
A는 메시지를 중간 저장소에 남기고, B는 자기 속도에 맞게 가져가서 처리합니다. 즉, 둘 사이를 직접 묶지 않고 한 번 끊어주는 방식입니다.
Kafka는 이 문제를 풀기 위해 많이 쓰이는 도구입니다.
정확히 말하면 Kafka는 단순한 “큐”라기보다 분산 이벤트 스트리밍 플랫폼에 가깝습니다. 메시지를 전달하는 용도로도 많이 쓰이지만, 그보다 더 큰 범위인 “이벤트를 저장하고, 여러 소비자가 독립적으로 읽고, 다시 처리할 수도 있는 구조”를 전제로 설계돼 있습니다.
Kafka를 이해할 때 꼭 알아야 하는 네 가지
Kafka를 처음 볼 때는 아래 네 개념만 정확히 잡아도 충분합니다.
Topic
메시지가 쌓이는 논리적인 통로입니다.
Partition
Topic은 내부적으로 여러 개의 파티션으로 나뉩니다.
Kafka가 처리량을 높이고 병렬 처리를 가능하게 하는 핵심은 바로 이 파티션 구조입니다. 메시지는 파티션 안에서 순서를 가지며, offset도 파티션 기준으로 관리됩니다.
Producer
메시지를 Topic으로 보내는 쪽입니다.
Producer는 “누가 읽는지”를 몰라도 됩니다. 어디에 쌓을지만 알면 됩니다.
Consumer
Topic에서 메시지를 읽어 처리하는 쪽입니다.
Consumer는 자신이 어디까지 읽었는지를 offset으로 관리합니다. 그래서 죽었다 살아나도 이어서 처리할 수 있습니다. Kafka는 메시지를 소비했다고 바로 지우지 않고, retention 정책에 따라 일정 기간 보관합니다. 즉, “읽었으니 사라진다”가 아니라 “읽었는지와 별개로 설정된 기간 동안 남겨둔다”에 가깝습니다.
Producer는 “상대에게 보내는” 것이 아니라 “여기 두고 가는” 쪽에 가깝다
직접 호출에서는 보통 이런 구조입니다.
- A가 B를 호출한다
- B가 바로 응답한다
- B 상태가 안 좋으면 A도 영향을 받는다
반면 Kafka에서는 Producer가 특정 Consumer를 직접 부르지 않습니다.
그냥 Topic에 메시지를 남깁니다.
이 차이가 중요합니다.
Producer는 Consumer가 지금 살아 있는지, 몇 개인지, 잠깐 느린지 알 필요가 없습니다. Topic에 기록만 하면 역할이 끝납니다. 이 덕분에 Producer와 Consumer 사이 결합이 느슨해집니다.
즉, 직접 호출이 “너에게 보낸다”라면, Kafka는 “여기 두고 간다”에 더 가깝습니다.
Consumer는 자기 속도에 맞게 읽는다
Consumer는 Topic을 구독하고 메시지를 읽습니다.
여기서 Kafka의 강점이 드러납니다.
Kafka는 메시지를 소비했다고 바로 삭제하지 않습니다.
메시지는 retention 기간 동안 로그에 남아 있고, Consumer는 offset만 앞으로 이동합니다. 그래서 Consumer가 잠깐 죽었다 살아나도 다시 이어서 읽을 수 있고, 필요하면 과거 시점으로 돌아가 재처리할 수도 있습니다.
이 구조는 “유실 없이 이어서 처리하기”와 “다시 읽기”에 매우 유리합니다.
Kafka가 많이 쓰이는 이유
Kafka가 많이 쓰이는 이유는 단순합니다.
첫째, 처리량이 높습니다.
Kafka는 로그 기반 저장 구조와 파티션 구조를 활용해 높은 처리량을 내도록 설계됐습니다. Apache Kafka도 공식적으로 high throughput을 핵심 특성으로 내세웁니다.
둘째, 메시지를 다시 읽을 수 있습니다.
읽었다고 바로 지워지는 모델이 아니라 retention 기간 동안 남겨두기 때문에, 재처리나 복구가 쉽습니다.
셋째, Producer와 Consumer가 서로 몰라도 됩니다.
나중에 Consumer를 하나 더 붙이거나, 다른 시스템을 연결해도 Producer 쪽 코드를 건드릴 필요가 없습니다.
정리
Kafka를 한 문장으로 정리하면 이렇습니다.
Producer가 이벤트를 남기고, Consumer가 자기 속도에 맞게 읽어가는 구조
이 구조 덕분에 Producer와 Consumer를 직접 묶지 않아도 되고, 한쪽이 느려지거나 잠깐 죽어도 전체가 같이 흔들리지 않게 만들 수 있습니다.
CDC가 뭔지, 왜 필요한지
운영 중인 시스템에서는 “DB가 바뀌었다”는 사실을 다른 시스템도 알아야 하는 경우가 많습니다.
예를 들면 이런 상황입니다.
- 원본 DB의 변경 내용을 분석용 DB에도 거의 실시간으로 반영하고 싶다
- 여러 시스템이 같은 원본 데이터의 변경을 따라가야 한다
이걸 가장 단순하게 구현하면 애플리케이션이 직접 두 곳에 다 쓰면 됩니다.
예를 들어 Oracle에 쓰는 동시에 PostgreSQL에도 쓰게 만들 수 있습니다.
그런데 이 방식은 금방 복잡해집니다.
- 한쪽만 성공하면 정합성이 틀어질 수 있습니다.
- 기존 업무 코드가 점점 더 많은 외부 시스템을 알게 됩니다.
- 타깃 시스템이 늘어날수록 애플리케이션 코드가 비대해집니다.
이럴 때 등장하는 개념이 CDC입니다.
CDC는 “애플리케이션 바깥”에서 변경을 잡아내는 방식이다
CDC는 Change Data Capture의 약자입니다.
말 그대로 DB에서 일어난 변경을 잡아내는 방식입니다.
핵심은 이것입니다.
애플리케이션이 직접 “나 이 테이블 바꿨어”라고 알리지 않아도,
DB가 남기는 변경 기록을 읽어 변경 이벤트를 만들 수 있다.
대부분의 관계형 DB는 트랜잭션 로그를 남깁니다.
Oracle은 Redo Log, MySQL은 Binlog, PostgreSQL은 WAL 같은 식입니다. CDC 도구는 이 로그를 읽어서 어떤 테이블에서 어떤 변경이 일어났는지 추출합니다. Debezium도 공식적으로 row-level change를 change event stream으로 기록한다고 설명합니다.
즉, 누군가 UPDATE USERS ...를 실행하면 CDC는 이걸 “DB에서 이런 변경이 발생했다”는 이벤트로 바꿔낼 수 있습니다.
왜 굳이 로그 기반으로 감지할까
가장 큰 이유는 애플리케이션을 건드리지 않아도 되기 때문입니다.
레거시 시스템이나 오래된 서비스는 이미 업무 코드가 복잡한 경우가 많습니다.
이때 애플리케이션 레벨에서 모든 변경 포인트를 찾아 수정하는 것보다, DB 로그에서 일어난 변경을 읽는 쪽이 더 안전하고 일관된 경우가 많습니다.
또 하나 중요한 이유는 변경 순서입니다.
로그 기반 CDC는 보통 DB가 기록한 순서를 따라 이벤트를 내보냅니다. Debezium도 애플리케이션이 변경 스트림을 “발생한 순서대로” 읽을 수 있다고 설명합니다. 이 순서가 어긋나면 후속 시스템의 정합성이 쉽게 깨질 수 있기 때문에, 로그 기반 접근은 여기서 강점을 가집니다.
CDC의 장점
실무에서 CDC가 강한 이유는 보통 세 가지입니다.
1) 기존 애플리케이션 코드를 덜 건드린다
업무 코드 안에 복제/동기화 로직을 넣지 않아도 됩니다.
2) 변경 순서를 따라가기 쉽다
트랜잭션 로그 기반이기 때문에, “무슨 일이 먼저 일어났는지”를 보존하기 유리합니다.
3) 전체를 다시 긁지 않고 변경만 전달할 수 있다
배치처럼 매번 전체 테이블을 읽는 방식보다 훨씬 효율적입니다.
CDC의 한계
CDC도 만능은 아닙니다.
첫째, DB별 차이가 큽니다.
Oracle, MySQL, PostgreSQL은 로그 구조가 다르고, 연결 방식도 다릅니다.
둘째, 초기 설정이 어렵습니다.
권한, 로그 보존, 보조 로깅, 스키마 매핑 같은 준비가 필요합니다.
셋째, DDL은 더 신경 써야 합니다.
스키마 변경이 들어오면 데이터만 보는 것보다 훨씬 복잡해집니다. Debezium 문서도 MySQL에서 DDL이 로그에 기록되며, 오래된 이벤트에는 현재 스키마를 그대로 적용할 수 없을 수 있다고 설명합니다. 즉, DDL이 “불가능”은 아니지만, 운영 난이도는 확실히 올라갑니다.
정리
CDC를 한 문장으로 정리하면 이렇습니다.
애플리케이션이 아니라 DB 로그를 읽어서 변경 이벤트를 뽑아내는 방식
이 방식의 장점은 기존 업무 코드를 덜 건드리면서, 변경을 거의 실시간에 가깝게, 그리고 순서 있게 가져갈 수 있다는 점입니다.
다음 글에서는 이 CDC 이벤트를 Kafka에 흘려보내면 왜 구조가 더 좋아지는지를 설명하겠습니다.
Kafka와 CDC를 왜 같이 쓰는가
CDC는 DB 변경을 감지합니다.
그런데 변경을 감지했다고 해서 그 자체로 끝나는 건 아닙니다.
그 다음 질문은 늘 같습니다.
그 이벤트를 어디로 보낼 것인가?
가장 단순한 구조는 CDC가 직접 타깃 DB나 타깃 시스템으로 보내는 것입니다.
- 변경 감지
- 즉시 타깃 반영
처음에는 단순해 보이지만, 이 구조는 직접 호출의 문제를 다시 가져옵니다.
- 타깃 시스템이 죽어 있으면 어떻게 할까
- 타깃이 여러 개면 CDC가 모두를 알아야 하나
- 처리 속도보다 변경이 더 빨리 쌓이면 어떻게 할까
이때 Kafka가 자연스럽게 등장합니다.
CDC는 변경을 만들고, Kafka는 안전하게 흘려보낸다
이 조합을 가장 짧게 설명하면 이렇게 됩니다.
- CDC는 변경을 감지해서 이벤트를 만든다
- Kafka는 그 이벤트를 저장하고 여러 소비자에게 흘려보낸다
즉, 둘의 역할이 다릅니다.
CDC가 “변경을 아는 것”이라면,
Kafka는 “그 변경을 어디까지, 어떤 속도로, 누구에게 전달할 것인가”를 담당합니다.
CDC는 Topic에 이벤트를 남기면 끝입니다. 이후 누가 읽는지는 Kafka와 Consumer 쪽 책임이 됩니다.
이 조합이 좋은 이유
1) 타깃 시스템끼리 서로 영향을 덜 준다
Consumer A가 느리거나 잠깐 죽어도 Consumer B는 계속 읽을 수 있습니다.
같은 이벤트를 다른 Consumer Group이 독립적으로 처리할 수 있기 때문입니다.
2) 속도 차이를 버틸 수 있다
원본 DB에서 변경이 한꺼번에 많이 발생하더라도, Kafka가 중간 버퍼 역할을 해줄 수 있습니다. Producer 쪽은 계속 기록하고, Consumer는 자기 속도에 맞게 읽어갑니다.
3) 재처리와 복구가 쉽다
Kafka는 소비했다고 바로 지우지 않습니다. retention 기간 동안 이벤트가 남아 있기 때문에, Consumer가 죽었다 살아나도 offset 이후부터 다시 읽을 수 있고, 필요하면 과거 offset으로 돌아가 재처리도 가능합니다.
4) 타깃 시스템을 늘리기 쉽다
새로운 타깃이 필요하면 Consumer를 하나 더 붙이면 됩니다. 원본 DB나 CDC 쪽 코드를 바꾸지 않아도 됩니다.
이 프로젝트 관점에서 보면
질문하신 프로젝트처럼 OEAConsumer가 Consumer 역할을 맡는 구조라면, 이 파이프라인의 의미는 더 분명합니다.
- OGG가 원본 DB 변경을 잡아낸다
- 변경을 Kafka Topic으로 보낸다
- OEAConsumer는 그 Topic을 읽어 타깃 DB에 반영한다
즉, OEAConsumer는 “변경을 감지하는 역할”이 아니라,
이미 만들어진 변경 이벤트를 읽어 후속 반영을 수행하는 Consumer 역할에 가깝습니다.
이렇게 역할을 나누면 구조가 훨씬 명확해집니다.
정리
CDC와 Kafka를 같이 쓰는 이유를 한 줄로 정리하면 이렇습니다.
CDC는 DB 변경을 이벤트로 만들고, Kafka는 그 이벤트를 여러 시스템이 독립적으로 안전하게 소비할 수 있게 만든다.
즉, CDC는 “무슨 일이 일어났는지”를 담당하고,
Kafka는 “그 일을 어떻게 흘려보낼지”를 담당합니다.
다음 글에서는 Kafka 없이 OGG 파일 방식만으로 처리할 때와 Kafka를 끼웠을 때 실무적으로 무엇이 달라지는지를 비교하겠습니다.
OGG 파일 방식 vs Kafka 방식, 실무에서는 뭐가 다를까
앞에서 CDC와 Kafka를 같이 쓰는 구조를 봤지만,
실무에서는 Kafka가 항상 필수는 아닙니다.
Oracle GoldenGate는 원본 DB의 변경을 trail file에 기록할 수 있고, 다른 프로세스가 이 trail을 읽어 처리할 수 있습니다. Oracle 문서도 Extract가 데이터 소스의 변경을 trail file에 기록하고, 다른 GoldenGate 프로세스가 이를 소비한다고 설명합니다. 또 체크포인트를 통해 어디까지 읽고 썼는지를 관리합니다.
즉, Kafka 없이도 “원본 변경을 읽어서 후속 처리한다”는 파이프라인은 충분히 만들 수 있습니다.
OGG 파일 방식
파일 방식은 구조가 비교적 단순합니다.
- OGG가 원본 DB 변경을 trail file에 기록한다
- Consumer가 그 파일을 읽는다
- 마지막 읽은 위치는 체크포인트로 관리한다
- 장애가 나면 다시 이어서 읽는다
이 방식의 장점은 명확합니다.
장점
- Kafka 브로커를 운영하지 않아도 된다
- 이미 OGG 인프라가 있다면 추가 구성이 적다
- 브로커 계층이 없어서 구조가 단순하다
- 폐쇄망/온프레미스 환경에서 상대적으로 도입 장벽이 낮다
단점
- 파일이 계속 쌓이므로 purge와 디스크 관리가 중요하다. Oracle도 trail file 관리와 purge 필요성을 별도로 다룹니다.
- 다수의 타깃 시스템이 같은 변경을 각각 소비해야 할 때 구조가 복잡해진다
- 병렬 처리와 확장을 설계하기가 Kafka보다 까다롭다
- 재생, 재처리, 다중 소비자 패턴을 직접 설계해야 하는 경우가 많다
즉, 파일 방식은 단순하고 직접적이지만,
타깃이 늘고 소비 패턴이 다양해질수록 운영 복잡도가 커질 수 있습니다.
Kafka 방식
Kafka 방식은 구조가 한 단계 더 들어갑니다.
- CDC 또는 OGG가 변경 이벤트를 Kafka Topic에 기록한다
- 여러 Consumer가 그 Topic을 읽는다
- 각 Consumer는 offset 기준으로 독립적으로 처리한다
장점은 아래처럼 정리할 수 있습니다.
장점
- 다중 소비자 구성이 쉽다
- 재처리와 replay가 쉽다
- Consumer Group으로 병렬 처리 구성을 하기 좋다
- Producer와 Consumer가 느슨하게 결합된다
- 타깃 시스템이 늘어나도 생산자 쪽 변경이 적다
단점
- Kafka 브로커 운영 부담이 생긴다
- 네트워크 홉이 늘어난다
- 장애 지점이 하나 더 생긴다
- 파티션, retention, consumer lag, 재처리 정책까지 함께 운영해야 한다
즉, Kafka 방식은 구조를 유연하게 만드는 대신 운영 요소가 늘어나는 방식입니다.
그럼 뭘 써야 할까
정답은 없습니다.
환경과 목표에 따라 달라집니다.
파일 방식이 잘 맞는 경우는 보통 이렇습니다.
- Kafka 인프라가 없다
- 구조를 최대한 단순하게 유지하고 싶다
- 타깃 시스템 수가 적다
- 폐쇄망/온프레미스 환경 제약이 크다
Kafka 방식이 잘 맞는 경우는 보통 이렇습니다.
- 타깃 시스템이 여러 개다
- 이벤트를 여러 방향으로 재사용해야 한다
- 재처리와 replay가 중요하다
- 처리량 증가에 맞춰 병렬 소비를 설계해야 한다
즉, 둘 중 무엇이 더 “좋다”보다,
지금 필요한 유연성을 얻기 위해 어느 정도 운영 복잡도를 감수할 것인가로 보는 게 맞습니다.
정리
OGG 파일 방식과 Kafka 방식은 결국 같은 목표를 향합니다.
원본 DB 변경을 안정적으로 읽어서 필요한 타깃 시스템에 반영하는 것
차이는 그 목표를 달성하는 방식입니다.
- 파일 방식은 단순하고 직접적이지만 확장성이 상대적으로 아쉽고
- Kafka 방식은 유연하고 확장성이 좋지만 운영 부담이 더 큽니다
실무에서는 기술 우열보다 환경 적합성이 더 중요합니다.
이미 OGG 중심 인프라가 잘 굴러가고 있고 타깃이 단순하다면 파일 방식도 충분히 좋은 선택입니다. 반대로 여러 소비자, 재처리, 이벤트 재사용이 중요해지는 순간 Kafka의 장점이 커집니다.