공부

Kafka란 무엇이고 언제 쓰는가: Docker로 1000만 건 부하 테스트까지

반응형
Kafka란 무엇이고 언제 쓰는가: Docker로 1000만 건 부하 테스트까지 해 본 결과

Apache Kafka · Docker 부하 테스트 · Consumer Lag

Kafka란 무엇이고 언제 쓰는가: 1000만 건 부하 테스트까지 해 본 결과

Kafka는 느린 일을 없애는 도구가 아니다. 느린 일을 사용자 요청 경로 밖으로 옮기고, 이벤트를 안전하게 쌓아두며, consumer가 처리 가능한 속도로 따라오게 만드는 도구다.

읽는데 약 17분 소요 · 작성 기준: 2026년 6월 13일 · 테스트 환경: 로컬 Docker, Apache Kafka 3.9.1, FastAPI, aiokafka

결론 먼저: Kafka는 이런 상황에서 쓴다

Kafka는 사용자가 지금 최종 결과까지 기다릴 필요는 없지만, 발생한 이벤트는 잃으면 안 되는 상황에서 쓴다. 핵심 트랜잭션은 빠르게 끝내고, 알림·통계·색인·정산 후보 생성 같은 후처리를 안전한 이벤트 로그 뒤로 분리할 때 가장 효과가 크다.

그래서 이번 글은 말로만 “Kafka가 빠르다”라고 설명하지 않기 위해 테스트용 주문 API를 직접 만들고 작성한 기록이다. Apache Kafka 공식 자료와 사용자가 공유한 YouTube 참고 영상을 함께 보며 개념 흐름을 정리했고, Kafka 사용 전에는 API가 후처리까지 기다리게 했고, Kafka 사용 후에는 이벤트를 topic에 넣고 바로 응답하게 했다. 아래 숫자는 그 차이를 확인하기 위한 로컬 Docker 부하 테스트 결과다.

예시 01

주문 접수 후 알림

주문 자체는 저장하고, 문자·푸시·메일 발송은 consumer가 뒤에서 처리한다.

왜 Kafka인가알림 시스템 지연이 주문 API 응답을 막지 않는다.
예시 02

순간 트래픽 버퍼

이벤트가 한 번에 몰려도 broker에 쌓아두고 consumer가 가능한 속도로 따라간다.

왜 Kafka인가사용자 요청 경로와 후처리 처리량을 분리한다.
예시 03

여러 서비스 구독

주문 이벤트 하나를 알림, 분석, 정산, 추천 서비스가 각자 읽는다.

왜 Kafka인가서비스끼리 직접 호출하는 연결을 줄인다.
예시 04

로그·클릭 데이터 적재

대량 행동 이벤트를 먼저 받아두고 데이터 파이프라인이 순차적으로 처리한다.

왜 Kafka인가고빈도 이벤트를 유실 없이 흘려보내기 쉽다.
예시 05

검색 색인 갱신

상품·게시글 변경 이벤트를 발행하고 검색 엔진 반영은 별도 consumer가 맡는다.

왜 Kafka인가검색 장애가 원본 데이터 변경을 막지 않는다.
예시 06

정산 후보 이벤트

결제 완료 뒤 정산·리포트·감사 로그에 필요한 사실을 이벤트로 남긴다.

왜 Kafka인가민감한 핵심 처리와 후속 집계를 분리한다.
반대로 조심할 때

결제 승인, 잔액 차감, 좌석 선점, 재고 확정처럼 사용자가 즉시 일관된 최종 상태를 받아야 하는 흐름은 Kafka만으로 응답을 끝내면 위험하다. 이런 일은 동기 트랜잭션을 먼저 지키고, Kafka는 그 뒤의 이벤트 전파나 감사 로그에 붙이는 편이 안전하다.

한 줄 결론

Kafka를 적용한 API는 1000만 건 요청을 1,217.206초에 모두 받아냈고 오류는 0건이었다. 평균 응답 시간은 36.5ms, p95는 68.5ms였다. 하지만 테스트 직후 Kafka lag는 9,711,044건 남아 있었다. 즉 Kafka는 API 응답 시간을 크게 줄였지만, 후처리 작업 자체를 없애지는 않았다.

Kafka 100만 건107.218초
Kafka 1000만 건1,217.206초
1000만 건 오류0건
1000만 건 p9568.5ms
Apache Kafka 공식 로고
▲ Apache Kafka 공식 로고. Kafka는 이벤트를 읽고, 쓰고, 저장하고, 처리하는 분산 이벤트 스트리밍 플랫폼이다. 출처

Kafka란 무엇인가

Apache Kafka 공식 문서는 Kafka를 여러 머신에 걸쳐 이벤트를 읽고, 쓰고, 저장하고, 처리하는 분산 이벤트 스트리밍 플랫폼으로 설명한다. 여기서 이벤트는 결제 거래, 위치 업데이트, 배송 주문, 센서 측정값처럼 시스템에서 일어난 사실을 뜻한다. Kafka는 이 이벤트를 topic에 저장하고, producer와 consumer가 topic을 사이에 두고 느슨하게 연결되도록 만든다.

일반적인 동기 호출에서는 서비스 A가 서비스 B를 직접 호출하고 B의 응답을 기다린다. B가 느리거나 장애가 나면 A의 응답도 느려진다. Kafka를 사이에 두면 A는 이벤트를 Kafka에 기록하고 빠르게 빠져나올 수 있다. B는 consumer가 되어 나중에 이벤트를 읽고 처리한다. 이 구조는 사용자 응답 경로와 후처리 경로를 분리한다.

Kafka producer와 consumer 관계
▲ Producer는 topic에 이벤트를 쓰고, consumer는 topic을 읽는다. 직접 호출 대신 이벤트 로그를 사이에 둔다. 출처

핵심 용어

용어의미이번 테스트의 예
ProducerKafka에 이벤트를 쓰는 애플리케이션/kafka/order API
Broker메시지를 저장하고 전달하는 Kafka 서버Docker의 apache/kafka:3.9.1 컨테이너
Topic이벤트 종류별 저장소order-events
Partitiontopic을 병렬 처리 가능하게 나눈 단위운영에서는 처리량과 순서 보장의 핵심
ConsumerKafka에서 이벤트를 읽는 애플리케이션주문 후처리 worker
Offsetconsumer가 어디까지 읽었는지 나타내는 위치lag 계산의 기준
Kafka 클러스터 구조 공식 다이어그램
▲ Kafka 클러스터 구조. broker, topic, partition, consumer group의 관계를 이해하면 lag와 확장 전략이 보인다. 출처

Kafka는 언제 쓰면 좋은가

Kafka는 “반드시 지금 최종 결과가 필요한 일”보다 “지금 접수하고 나중에 처리해도 되는 일”에 잘 맞는다. 주문 접수 후 알림, 추천 이벤트 적재, 검색 색인 갱신, 통계 집계, 로그 수집, 데이터 파이프라인처럼 비동기화 가능한 흐름에서 효과가 크다.

상황Kafka가 주는 이점주의점
주문 생성 후 알림과 통계 처리주문 API가 알림 시스템을 기다리지 않아도 된다사용자에게 “접수”와 “완료” 상태를 구분해야 한다
순간 트래픽 급증Kafka가 버퍼 역할을 해서 consumer가 가능한 속도로 따라간다lag가 계속 증가하면 결국 worker 확장이 필요하다
여러 서비스가 같은 이벤트를 구독주문 이벤트 하나를 알림, 분석, 정산 서비스가 각각 읽을 수 있다중복 처리와 이벤트 스키마 변경을 설계해야 한다
로그와 클릭 데이터 수집대량 이벤트를 안정적으로 적재하기 쉽다retention, 압축, 저장 비용을 관리해야 한다

반대로 결제 승인, 잔액 차감, 좌석 선점처럼 사용자가 즉시 일관된 최종 결과를 받아야 하는 흐름은 Kafka만으로 응답을 끝내면 위험하다. 이런 경우 Kafka는 핵심 트랜잭션 뒤의 이벤트 전파나 감사 로그에 붙이는 편이 안전하다.

Kafka consumer group 공식 이미지
▲ Consumer group은 partition을 나누어 읽는다. 처리량을 늘릴 때 partition과 consumer 수를 함께 봐야 한다. 출처

테스트 설계

테스트는 두 API를 비교했다. /sync/order는 요청 안에서 느린 후처리까지 끝낸 뒤 응답한다. /kafka/order는 Kafka에 이벤트를 넣고 바로 응답한다. 후처리 worker는 Kafka를 읽어 같은 80ms 작업을 수행한다.

Client
동시 요청
FastAPI
주문 API
Kafka topic
order-events
Consumer worker
후처리
느린 작업
80ms
항목
Kafka broker로컬 Docker의 apache/kafka:3.9.1 1대
API 서버FastAPI + Uvicorn + aiokafka
후처리 지연요청당 80ms
후처리 동시 처리량20개
고부하 요청 수100,000건, 1,000,000건, 10,000,000건
고부하 동시성300
cd work/kafka-loadtest
docker compose up -d --build
node high_volume_bench.mjs --path /kafka/order --requests 10000000 --concurrency 300 --progress-every 1000000 --output ../../outputs/kafka_high_volume_10m.json
Kafka 부하 테스트 실측 요약 차트
▲ 이번 로컬 Docker 실측 요약. Kafka는 API 완료 시간을 크게 줄였지만, 남은 lag는 별도로 처리해야 한다. 출처

부하 테스트 결과

먼저 1,000건 비교에서는 동기 방식의 평균 응답이 611.9ms, Kafka 방식의 평균 응답이 24.5ms였다. p95는 707.2ms에서 38.2ms로 줄었다. 이 작은 테스트만 봐도 사용자-facing API에서는 차이가 크다.

지표Kafka 사용 전: 동기 처리Kafka 사용 후: enqueue
요청 수1,0001,000
오류00
총 API 처리 시간4.384초0.299초
처리량228.1 RPS3,342.7 RPS
평균 응답611.9ms24.5ms
p95 응답707.2ms38.2ms

고부하 Kafka 테스트는 100,000건, 1,000,000건, 10,000,000건을 실제로 실행했다. 1000만 건에서도 성공 10,000,000건, 오류 0건이었다. 평균 응답은 36.5ms, p95는 68.5ms였다.

요청 수성공오류총 API 완료 시간처리량평균 응답p95
100,000100,000012.391초8,070.4 RPS34.0ms51.6ms
1,000,0001,000,0000107.218초9,326.8 RPS31.8ms48.9ms
10,000,00010,000,00001,217.206초8,215.5 RPS36.5ms68.5ms

동기 방식은 10,000건 기준선을 실제 측정했다. 결과는 43.661초, 229.0 RPS, 평균 응답 1,260.1ms였다. 이 처리량을 기준으로 단순 계산하면 100만 건은 약 72.8분, 1000만 건은 약 12.13시간이 걸린다. 반면 Kafka 방식은 100만 건을 약 1.79분, 1000만 건을 약 20.29분에 받았다.

요청 수동기 방식 예상Kafka 방식 실측API 완료 시간 개선
1,000,000약 72.8분약 1.79분약 40.7배
10,000,000약 12.13시간약 20.29분약 35.9배

1000만 건 후 남은 lag의 의미

여기서 가장 중요한 숫자는 응답 시간이 아니라 lag다. 1000만 건 테스트 직후 Kafka accepted는 10,000,000건, worker processed는 288,956건, 남은 lag는 9,711,044건이었다. worker 처리율은 약 237.4건/초였고, 같은 속도라면 남은 lag를 처리하는 데 약 11.36시간이 더 필요하다.

지표
Kafka accepted10,000,000
worker processed288,956
남은 lag9,711,044
worker 처리율약 237.4건/초
남은 lag 처리 예상약 11.36시간

이 결과는 Kafka의 장점과 한계를 동시에 보여 준다. API는 매우 빠르게 요청을 접수한다. 하지만 후처리 worker가 그대로라면 전체 업무 완료 시간은 줄어들지 않는다. 운영에서 Kafka를 도입한 뒤에는 API latency만 볼 것이 아니라 consumer lag를 반드시 봐야 한다.

Kafka 로그 구조 공식 이미지
▲ Kafka는 이벤트를 로그처럼 저장한다. consumer가 어디까지 읽었는지는 offset으로 추적된다. 출처

실무에서 이 결과를 읽는 방법

이 섹션은 긴 설명 대신 실무자가 바로 확인할 키워드로 다시 읽는 표입니다. 핵심은 API 성공, Kafka 적재, consumer 완료를 한 덩어리로 보지 않는 것입니다.

1 API

사용자는 얼마나 빨리 응답을 받았나

36.5ms 평균
2 Kafka

이벤트는 안전하게 쌓였나

1000만 건 accepted
3 Consumer

후처리는 따라잡고 있나

lag 9,711,044건
4 Decision

확장, 재처리, SLA를 정한다

회복 예상 11.36시간
01

이벤트 경계

무슨 일이 일어났는지 먼저 정의

볼 것이벤트 이름, 발행 시점, 필수 필드
판단주문 접수와 결제 승인을 같은 이벤트로 섞지 않는다.
액션event_id와 이벤트 버전을 명세한다.
02

상태 모델

사용자에게 보여줄 완료 기준

볼 것접수, 처리 중, 완료, 실패 상태
판단API가 빠르다는 이유로 후처리 완료처럼 보여주면 안 된다.
액션화면 문구를 접수 완료와 처리 완료로 나눈다.
03

Consumer Lag

빠른 API 뒤에 남은 실제 대기열

볼 것lag 개수, lag 소진 예상 시간, partition별 편차
판단1000만 건 테스트 후 lag 9,711,044건이 남았다.
액션lag / consumer 처리율로 회복 시간을 본다.
04

중복 처리

재시도는 정상 흐름으로 가정

볼 것producer retry, offset commit, 처리 이력
판단같은 메시지를 다시 읽어도 결과가 1번만 반영돼야 한다.
액션idempotency key와 처리 로그를 둔다.
05

Partition Key

순서 보장과 병렬 처리의 trade-off

볼 것key 분포, hot partition, consumer 수
판단order_id key는 순서에 좋지만 특정 key 쏠림이 생길 수 있다.
액션업무 순서가 필요한 범위만 같은 key로 묶는다.
06

Backpressure

읽는 속도와 처리 속도를 맞추기

볼 것in-flight 작업 수, DB pool, 외부 API rate limit
판단consumer가 빨리 읽어도 downstream이 느리면 병목은 이동한다.
액션poll, batch, 동시 처리 수를 함께 제한한다.
07

DLQ

실패 메시지를 버리지 않는 회수 장치

볼 것재시도 횟수, 실패 원인, DLQ 유입량
판단poison message 하나가 전체 consumer를 막으면 안 된다.
액션제한 재시도 후 DLQ로 보내고 재처리 절차를 만든다.
08

운영 비용

빠르게 받은 만큼 broker에 저장된다

볼 것retention, compression, disk I/O, replication
판단1000만 건은 API가 끝난 뒤에도 Kafka 로그로 남는다.
액션보관 기간과 압축, 디스크 알림을 먼저 정한다.
09

관찰 지표

성공률만 보면 절반만 본 것

볼 것producer latency, broker I/O, consumer throughput
판단HTTP 200 OK와 시스템 완료는 다른 지표다.
액션API 지표와 Kafka 지표를 같은 대시보드에 둔다.
10

도입 순서

위험이 낮은 이벤트부터 분리

볼 것알림, 로그, 분석, 색인 갱신
판단결제 승인 같은 핵심 트랜잭션부터 옮기면 위험하다.
액션재처리 가능한 후처리부터 Kafka로 분리한다.
빠른 응답 API latency

사용자가 체감하는 속도

안전한 적재 Kafka append

이벤트가 사라지지 않는가

완료 보장 Consumer drain

쌓인 일을 SLA 안에 비우는가

키워드별 해설 펼쳐보기

이벤트와 상태+

실무에서 Kafka를 검토할 때는 “이벤트를 발행하면 된다”에서 멈추면 안 된다. 가장 먼저 정해야 하는 것은 이벤트의 의미다. 예를 들어 주문 도메인에서 “주문 생성 요청을 받았다”, “주문이 결제 가능한 상태가 되었다”, “결제가 승인되었다”, “배송 준비가 시작되었다”는 서로 다른 사건이다. 이 경계가 흐리면 consumer는 이벤트 이름만 보고 잘못된 업무를 시작하고, 장애가 났을 때 어디까지 되돌려야 하는지도 모호해진다.

두 번째는 사용자에게 보여줄 상태 모델이다. Kafka를 쓰면 API는 빠르게 응답할 수 있지만 실제 후처리는 나중에 끝난다. 이때 화면에서 “완료”라고 보여줄지 “접수”라고 보여줄지는 단순한 문구 문제가 아니다. 알림, 통계, 색인, 정산처럼 늦어져도 되는 작업은 접수 완료 뒤에 비동기로 처리해도 된다. 그러나 결제 승인, 재고 차감, 좌석 배정처럼 사용자가 즉시 의존하는 상태는 동기 경로에 남겨야 한다.

Lag와 처리 보장+

세 번째는 lag의 의미를 팀 전체가 이해하는 것이다. lag는 Kafka에 쌓인 미처리 메시지 수다. lag가 일시적으로 증가했다가 줄어드는 것은 정상일 수 있다. 이벤트 유입량이 순간적으로 커졌고 consumer가 천천히 따라잡는 상황이기 때문이다. 하지만 lag가 계속 증가하거나 특정 partition에서만 줄지 않는다면 consumer 장애, downstream 지연, partition key 쏠림, poison message 같은 문제를 의심해야 한다.

네 번째는 처리 보장 수준이다. 많은 시스템은 정확히 한 번 처리된 것처럼 보이길 원하지만, 실제 분산 환경에서는 재시도와 중복을 전제로 설계하는 편이 안전하다. producer가 타임아웃을 받았지만 broker에는 메시지가 들어갔을 수 있고, consumer가 외부 API 호출을 끝낸 뒤 offset commit 전에 죽을 수도 있다. 따라서 “중복이 들어오지 않는다”가 아니라 “중복이 들어와도 결과가 망가지지 않는다”를 목표로 잡아야 한다.

스키마와 Partition+

다섯 번째는 스키마 진화다. 이벤트는 한 번 발행되면 여러 consumer가 독립적으로 읽는다. producer가 필드를 갑자기 삭제하거나 타입을 바꾸면 과거 consumer가 깨질 수 있다. 그래서 새 필드는 optional로 추가하고, 기존 필드는 충분한 호환 기간을 둔 뒤 제거하는 방식이 안전하다. 규모가 커지면 Schema Registry 같은 도구를 도입해 호환성 규칙을 자동으로 검증하는 편이 좋다.

여섯 번째는 partition key 설계다. 같은 주문의 이벤트 순서가 중요하다면 order_id를 key로 잡아 같은 partition에 들어가게 할 수 있다. 이렇게 하면 같은 key 안의 순서는 지키기 쉽지만, 특정 key에 트래픽이 몰리면 한 partition이 병목이 된다. 반대로 key를 분산 위주로 잡으면 처리량은 좋아질 수 있지만 업무 순서를 애플리케이션에서 따로 보장해야 할 수 있다.

Backpressure와 DLQ+

일곱 번째는 batch와 backpressure다. consumer가 메시지를 너무 빨리 읽어 메모리에 쌓아두면 Kafka lag는 줄어드는 것처럼 보이지만 실제 downstream 처리는 밀릴 수 있다. 이번 테스트에서도 consumer task를 무제한으로 만들지 않도록 in-flight 제한을 넣었다. 운영에서도 consumer poll, batch size, 동시 처리 수, DB connection pool, 외부 API rate limit을 함께 맞춰야 한다.

여덟 번째는 장애 메시지의 수명이다. 특정 이벤트가 항상 실패한다면 consumer가 같은 메시지에서 계속 막힐 수 있다. 이때 무한 재시도만 하면 뒤의 정상 메시지까지 지연된다. 보통은 제한된 횟수만 재시도하고, 그래도 실패하면 DLQ로 보내 원인과 payload를 보존한다. DLQ는 쓰레기통이 아니라 운영자가 다시 처리할 수 있는 별도 작업 큐로 보아야 한다.

비용, 보안, 테스트+

아홉 번째는 비용이다. Kafka는 이벤트를 저장하므로 retention 설정, 압축, 메시지 크기, replication factor가 디스크와 네트워크 비용에 직접 영향을 준다. 1000만 건 테스트에서도 API는 빠르게 끝났지만 Kafka broker에는 대량의 로그가 쌓였다. 운영에서는 디스크 사용량, segment 크기, cleanup policy, 압축 방식, broker I/O를 관찰해야 한다.

마지막으로 성능 테스트를 설계할 때는 API 완료 시간과 end-to-end 완료 시간을 분리해 기록해야 한다. API 완료 시간은 사용자가 응답을 받는 시간이고, end-to-end 완료 시간은 consumer가 후처리까지 끝낸 시간이다. 둘을 섞으면 Kafka 도입 효과를 과대평가하게 된다. 이번 실험에서 API는 1000만 건을 약 20.29분에 받았지만, 남은 lag를 모두 처리하려면 현재 worker 설정으로 약 11.36시간이 더 필요하다는 점이 바로 그 차이다.

메시지 크기도 현실적인 병목이다. 작은 JSON 이벤트는 빠르게 처리되지만, 큰 payload를 그대로 Kafka에 넣으면 network, broker disk, consumer memory에 부담이 된다. 일반적으로 이벤트에는 업무 판단에 필요한 핵심 식별자와 상태를 넣고, 큰 원문 데이터는 object storage나 DB에 두고 reference를 담는 식으로 설계한다. 그래야 topic retention 비용도 예측 가능하다.

보안도 빼놓을 수 없다. 내부 이벤트라고 해서 개인정보나 결제 정보를 그대로 넣으면 retention 기간 동안 민감 데이터가 장기간 남는다. 운영 Kafka에서는 인증, 권한, 암호화, 접근 로그, 개인정보 마스킹 정책이 필요하다. 이벤트가 여러 팀으로 흘러가기 때문에 producer가 한 번 발행한 데이터의 노출 범위가 생각보다 넓어질 수 있다.

테스트 데이터도 실제와 닮아야 한다. 이번 실험은 작은 주문 JSON과 80ms sleep으로 병목을 단순화했다. 운영 전 검증에서는 실제 payload 크기, 실제 DB 쓰기, 외부 API rate limit, consumer batch 처리, broker replication, 네트워크 지연을 반영해야 한다. 특히 단일 broker 로컬 테스트에서 나온 RPS를 운영 수치로 그대로 약속하면 안 된다.

도입 순서와 운영 기준+

Kafka를 처음 도입할 때는 모든 흐름을 한 번에 옮기지 않는 편이 좋다. 먼저 사용자 응답과 직접 관련이 적은 알림, 분석 이벤트, 검색 색인 갱신처럼 실패해도 재처리 가능한 흐름부터 분리한다. 그 다음 주문 상태 변경이나 정산처럼 더 민감한 이벤트로 확장한다. 작은 성공을 통해 event_id, schema, DLQ, monitoring 같은 공통 규칙을 먼저 만드는 것이 장기적으로 안전하다.

팀 내 커뮤니케이션도 중요하다. Kafka topic은 단순한 기술 리소스가 아니라 팀 간 계약이다. Producer 팀은 어떤 이벤트를 언제 발행하는지 문서화해야 하고, consumer 팀은 어떤 필드를 신뢰하는지 명확히 해야 한다. 이벤트 필드 하나를 바꾸는 일이 여러 서비스 장애로 이어질 수 있으므로, API 명세처럼 이벤트 명세도 버전과 변경 이력을 관리해야 한다.

모니터링 대시보드는 최소한 producer 성공률, producer latency, broker disk 사용량, consumer lag, consumer 처리량, DLQ 유입량을 보여줘야 한다. API latency만 낮고 lag가 계속 증가하는 상태는 겉보기에는 성공처럼 보이는 실패다. 반대로 lag가 잠깐 증가했지만 빠르게 줄어든다면 시스템이 버스트 트래픽을 잘 흡수하고 있다고 해석할 수 있다.

알림 기준은 절대값 하나로만 잡기 어렵다. lag 10,000건이 큰 문제인지 아닌지는 consumer 처리율과 업무 SLA에 따라 다르다. consumer가 초당 5,000건 처리한다면 10,000건 lag는 2초 backlog지만, 초당 50건 처리한다면 200초 backlog다. 따라서 lag 개수와 함께 예상 소진 시간, 즉 lag divided by consumer throughput을 같이 보는 편이 좋다.

Kafka를 붙였는데 오히려 시스템이 어려워지는 경우도 있다. 처리 순서가 복잡하고 보상 트랜잭션이 많으며, 업무 담당자가 비동기 상태를 받아들이기 어렵다면 단순한 동기 호출이 더 나을 수 있다. Kafka는 강력하지만 운영 지식과 관찰 가능성이 필요하다. 작은 서비스에서 트래픽도 낮고 후처리도 거의 없다면 도입 비용이 이득보다 클 수 있다.

그래도 대량 이벤트가 있는 시스템에서는 Kafka가 제공하는 완충 효과가 크다. 갑자기 트래픽이 몰려도 broker가 이벤트를 저장하고, consumer는 처리 가능한 속도로 따라갈 수 있다. 여러 consumer group이 같은 이벤트를 각자 읽을 수 있다는 점도 장점이다. 주문 이벤트 하나로 알림, 통계, 추천, 정산이 서로를 직접 호출하지 않고 독립적으로 움직일 수 있다.

이번 테스트의 숫자는 이 균형을 잘 보여준다. Kafka API는 1000만 건을 오류 없이 받았고 평균 응답도 36.5ms였다. 하지만 후처리 lag는 971만 건 이상이었다. 이 두 문장을 동시에 받아들여야 한다. Kafka 도입 성공은 “응답이 빨라졌다”에서 끝나지 않고, “쌓인 일을 계획한 시간 안에 처리한다”까지 확인해야 완성된다.

도입 절차를 현실적으로 잡는다면 1단계는 관찰 가능한 비동기 작업 하나를 고르는 것이다. 예를 들어 주문 완료 알림이나 행동 로그 적재처럼 실패해도 사용자가 즉시 금전적 손해를 보지 않는 작업이 좋다. 2단계는 이벤트 형식과 event_id를 정하고, 3단계는 consumer 처리 결과를 기록하며, 4단계는 DLQ와 재처리 화면 또는 운영 명령을 준비하는 것이다.

5단계는 작은 부하 테스트다. 처음부터 1000만 건을 목표로 잡기보다 1,000건, 10,000건, 100,000건으로 늘리며 API latency와 lag가 어떻게 움직이는지 본다. 이때 테스트가 끝난 순간의 API 결과만 저장하지 말고, 테스트 후 1분, 5분, 10분 뒤 lag가 줄어드는지도 남겨야 한다. 그래야 consumer가 backlog를 회복할 수 있는지 판단할 수 있다.

6단계는 장애 주입이다. consumer를 중간에 죽였다가 다시 띄우고, broker를 재시작하고, 외부 API가 느려지는 상황을 만든다. 이 과정에서 메시지가 사라지는지, 중복 처리되어도 결과가 안전한지, DLQ로 빠진 메시지를 사람이 이해할 수 있는지 확인한다. Kafka는 장애를 피하는 도구가 아니라 장애 후 다시 따라잡을 수 있게 해 주는 도구에 가깝다.

성능 결과를 공유할 때는 숫자 옆에 전제를 꼭 붙여야 한다. 이번 1000만 건 결과는 로컬 Docker, broker 1대, 작은 JSON payload, access log 비활성화, 단순 sleep 후처리라는 조건에서 나온 값이다. 운영 환경에서 replication factor를 올리고, TLS와 인증을 켜고, 실제 DB 쓰기와 외부 API를 붙이면 수치는 달라진다. 그래서 중요한 것은 절대 RPS보다 병목이 어디로 이동했는지다.

Kafka를 잘 쓰는 팀은 보통 “빠른 API”보다 “명확한 이벤트 흐름”을 먼저 이야기한다. 어떤 서비스가 어떤 사실을 발행하고, 누가 읽으며, 실패하면 어디로 가고, 언제까지 처리되어야 하는지 합의되어 있다. 이 합의가 있으면 Kafka는 서비스 간 결합도를 낮춘다. 합의 없이 topic만 늘어나면 Kafka는 오히려 디버깅하기 어려운 숨은 의존성 저장소가 된다.

따라서 이 글의 결론을 운영 언어로 바꾸면 이렇다. Kafka는 요청 폭주를 견디는 buffer, 서비스 간 직접 호출을 줄이는 event log, 여러 소비자가 같은 사실을 각자 읽게 하는 distribution layer다. 하지만 업무 완료를 보장하는 것은 consumer 설계, idempotency, monitoring, retry, DLQ다. Kafka 자체와 Kafka를 둘러싼 운영 설계를 한 묶음으로 보아야 한다.

원본 결과를 읽을 때는 success_count와 error_count만 보지 말고 metrics_after_api_load를 함께 보아야 한다. 이 값에는 Kafka가 받은 수, worker가 처리한 수, 남은 lag가 들어 있다. API 벤치마크 도구 입장에서는 200 OK가 성공이지만, 시스템 운영자 입장에서는 lag가 줄어드는지까지가 성공이다. 같은 결과 파일이라도 보는 관점에 따라 결론이 달라진다.

재현 테스트를 한다면 access log도 주의해야 한다. 1000만 건 요청을 모두 로그로 남기면 애플리케이션보다 로그 I/O가 먼저 병목이 될 수 있다. 이번 테스트에서는 Uvicorn access log를 끄고, 벤치마크 스크립트도 전체 요청 결과를 메모리에 저장하지 않도록 샘플링 방식으로 바꾸었다. 고부하 테스트에서는 측정 도구가 시스템보다 무거워지는 상황을 피해야 한다.

마지막으로, Kafka 도입은 성능 개선 프로젝트이면서 동시에 제품 상태 모델을 바꾸는 작업이다. 사용자는 빠른 응답을 좋아하지만, 비동기 처리가 실패했을 때 어떤 상태를 보게 될지도 중요하다. 그래서 Kafka 적용 전에는 사용자 메시지, 관리자 재처리 도구, 실패 알림, 데이터 보정 절차까지 같이 준비해야 한다. 그래야 빠른 시스템이면서도 믿을 수 있는 시스템이 된다.

의사결정 기준도 숫자로 두는 편이 좋다. 예를 들어 “주문 API p95는 100ms 이하”, “알림 이벤트는 5분 안에 99% 처리”, “DLQ 유입은 0.1% 이하”, “lag 예상 소진 시간은 10분 이하”처럼 API와 후처리를 나누어 목표를 세운다. 그러면 Kafka를 붙인 뒤 좋아진 지표와 아직 남은 병목을 팀이 같은 언어로 볼 수 있다.

이번 실험에서 동기 방식과 Kafka 방식의 차이는 단순했다. 동기 방식은 사용자가 후처리 대기열 앞에 서 있었고, Kafka 방식은 사용자를 먼저 보내고 후처리 대기열을 Kafka 뒤로 옮겼다. 그래서 빠른 응답은 Kafka의 장점이고, 남은 lag는 우리가 계속 운영해야 할 책임이다.

결국 Kafka의 가치는 대량 이벤트를 잃지 않고 받아들이는 능력과 서비스 간 의존성을 낮추는 데 있다. 이 장점을 제대로 쓰려면 빠른 producer만큼 성실한 consumer가 필요하다. 둘 중 하나만 보면 절반의 결론만 얻게 된다. 그래서 성능표와 운영표를 항상 같이 읽어야 한다. 이 균형이 Kafka 설계의 출발점이다. 숫자는 빠르게, 책임은 끝까지 보자. 끝까지 같이 확인하자.

운영 도입 체크리스트

Kafka를 붙이는 순간부터 시스템은 동기 호출 중심에서 이벤트 중심으로 바뀐다. 그래서 성능뿐 아니라 실패 처리와 데이터 의미를 함께 설계해야 한다.

  1. 이벤트 경계 정하기: 무엇을 동기 트랜잭션으로 보장하고, 무엇을 비동기 이벤트로 보낼지 나눈다.
  2. idempotency 설계: 같은 이벤트가 2번 처리되어도 결과가 1번만 반영되게 만든다.
  3. partition key 결정: 주문별 순서가 중요한지, 처리량이 더 중요한지 기준을 잡는다.
  4. consumer lag 알림: lag가 일정 기준을 넘으면 알림을 보내고 worker 확장 여부를 판단한다.
  5. DLQ와 재처리: 실패 메시지를 버리지 않고 별도 topic이나 저장소에 남긴다.
  6. 스키마 관리: 이벤트 필드가 바뀌어도 기존 consumer가 깨지지 않도록 호환성을 지킨다.
  7. 운영 비용 확인: retention, 압축, broker 디스크, 네트워크 사용량을 본다.

Docker Compose는 이번처럼 broker와 테스트 API를 함께 띄우는 실험 환경에 편하다. Docker 공식 문서는 Compose를 여러 컨테이너 애플리케이션을 정의하고 실행하는 도구로 설명한다. 운영에서는 이 구성을 그대로 쓰기보다 Kubernetes, managed Kafka, 모니터링 스택과 함께 재설계하는 편이 보통이다.

services:
  kafka:
    image: apache/kafka:3.9.1
    ports:
      - "9092:9092"

  app:
    build:
      context: ./app
    ports:
      - "8000:8000"
    environment:
      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
      KAFKA_TOPIC: order-events
Kafka API 공식 이미지
▲ Kafka 생태계의 주요 API. 이번 테스트는 producer와 consumer를 중심으로 API 응답 경로를 비교했다. 출처

FAQ

Q1. Kafka를 쓰면 API가 항상 빨라지나요?+

사용자 요청 경로에서 느린 후처리를 분리할 수 있다면 빨라질 가능성이 큽니다. 하지만 요청 안에서 반드시 최종 결과를 계산해야 한다면 Kafka만으로 응답 시간을 줄일 수 없습니다. 이번 테스트에서도 API 응답은 빨라졌지만 후처리 lag는 남았습니다.

Q2. Kafka와 일반 메시지 큐는 무엇이 다른가요?+

Kafka는 이벤트를 로그처럼 저장하고 consumer가 offset을 기준으로 읽습니다. 같은 이벤트를 여러 consumer group이 각자 읽을 수 있고, retention 기간 동안 다시 읽는 것도 가능합니다. 단순 작업 큐보다 이벤트 스트리밍과 데이터 파이프라인에 강합니다.

Q3. 1000만 건 테스트에서 오류가 0건이면 운영도 안전한가요?+

아닙니다. 이번 결과는 로컬 Docker 단일 broker 환경의 기능 비교용 실험입니다. 운영에서는 broker 장애, consumer 재시작, 네트워크 지연, 디스크 부족, 스키마 변경, 중복 처리, DLQ까지 검증해야 합니다.

Q4. lag가 쌓이면 어떻게 해야 하나요?+

먼저 worker 처리율과 downstream 병목을 봐야 합니다. 그 다음 partition 수, consumer 수, batch 처리, 외부 API 제한, DB connection pool을 함께 조정합니다. partition이 충분하지 않으면 consumer를 늘려도 병렬성이 나오지 않습니다.

Q5. Kafka 도입 전 가장 먼저 정해야 할 것은 무엇인가요?+

이벤트의 의미와 처리 보장 수준입니다. 예를 들어 “주문이 접수되었다”와 “결제가 최종 승인되었다”는 다른 이벤트입니다. 어떤 이벤트를 언제 발행하고, 실패하면 어떻게 재처리할지 먼저 정해야 Kafka가 시스템을 단순하게 만듭니다.

참고 자료와 원본 데이터

반응형

Categories