Kafka는 대규모 데이터 스트림을 처리하기 위한 분산형 메시징 플랫폼을 말한다.
단순한 메시지 큐가 아닌 분산 로그 시스템이기 때문에 내부 구성 요소들이 "고성능 / 장애 복구 / 확장성"을 중심으로 설계되어 있다.
![]() |
|---|
order-created, payment-completedProducer → [Kafka Broker] → Consumer
| Topic |
┌──────────────┐
│ Partition 0 │ → 메시지 A, B, C ...
│ Partition 1 │ → 메시지 D, E, F ...
└──────────────┘
Kafka에서 파티션 전략은 성능 / 순서 / 확장성 / 데이터 일관성을 동시에 결정하는 핵심 설계 요소이다.
그렇기 때문에 단순히 "많이 나누면 좋다"가 아니라, 비즈니스 특성과 소비 패턴을 고려해 설계해야 한다.
kafkaTemplate.send("order-topic", value);
kafkaTemplate.send("order-topic", userId, value);
"이 단위 안에서는 순서를 반드시 보장해야 한다" → 그 단위를 key로 사용
![]() |
|---|
예: userId 기준으로 분산했는데 특정 user가 트래픽 80% 차지
→ 결과:
Partition 0 → 80% 부하
나머지 → 한가함
이를 Hot Partition이라고 한다.
Hot Partition 문제에 대한 해결 전략으로 3개 정도 서술하자면,
userId + randomSuffix
단점: 순서 보장 깨질 수 있음
userId + orderDate
시간 단위로 분산
public class CustomPartitioner implements Partitioner {
@Override
public int partition(...) {
// 직접 분산 로직 작성
}
}
특정 로직 기반 분산 가능
파티션 수는 늘릴 수는 있지만 줄일 때는 위험(순서 보장 깨질 수 있음, 데이터 분산 재정렬)이 따르기 때문에 처음 설계가 매우 중요하다.
파티션 전략 설게 시 고려할 점:
목표 처리량을 기반으로 계산해서 설계할 때는:
목표 TPS ÷ 파티션당 처리 가능 TPS = 최소 파티션 수Consumer 수 고려해서 설계할 때는
| 문제 | 설명 |
|---|---|
| 파일 핸들 증가 | OS 리소스 증가 |
| 메모리 사용 증가 | 브로커 부하 |
| Rebalance 시간 증가 | 장애 시 지연 |
| 관리 복잡도 증가 | 운영 비용 증가 |
일반적으로 수십~수백 개는 흔함. 수천 개 이상은 신중해야 함
Kafka + Outbox 패턴 설계는 데이터 정합성과 확장성을 동시에 만족시키기 위한 핵심 설계이다.
특히 주문/결제/재고처럼 정합성이 중요한 도메인에서 거의 표준에 가깝다고 생각한다.
Outbox 패턴의 핵심은:
이벤트를 DB 트랜잭션 안에서 함께 저장하고 나중에 별도 프로세스가 Kafka로 발행하는 것에 있다.
[Order Service]
├─ Order 저장
└─ Outbox 저장 (aggregate_id 포함)
[Outbox Publisher]
└─ Kafka 전송 (key = aggregate_id)
[Kafka]
└─ 같은 aggregate는 같은 Partition
[Consumer]
└─ event_id 기반 멱등 처리
Kafka 장애 시 Outbox 재처리 전략의 핵심은:
1. 데이터 유실 없이
2. 중복은 허용하되 멱등으로 해결한다
3. 무한 재시도는 막는다
4. 락 전략을 명확히 한다
DB TX 성공
↓
Outbox 저장
↓
Kafka 장애 발생
↓
Outbox 재시도
↓
Kafka 복구
↓
정상 전송
↓
Consumer 멱등 처리
[상황]
[해결 전략]
status = READY 상태 유지 → Publisher가 재시도[상황]
[해결 전략]
[상황]
[해결 전략]
1. READY 조회
2. PROCESSING 변경 (락 확보)
3. Kafka 전송 시도
4. 성공 → SENT
5. 실패 → READY (retry_count++)
멀티 인스턴스 환경에서는 같은 row를 여러 Publisher가 가져가면 안 된다.
SELECT *
FROM outbox
WHERE status = 'READY'
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT 100;
동시에 여러 인스턴스가 실행해도 중복 처리 방지
SKIP LOCKED → 이미 잡힌 row는 건너뜀UPDATE outbox
SET status = 'PROCESSING'
WHERE id = ?
AND status = 'READY';
update count == 1 이면 성공적으로 점유
| 항목 | Kafka | RabbitMQ |
|---|---|---|
| 구조 | 로그 기반 분산 스트리밍 | 큐 기반 메시지 브로커 |
| 메시지 순서 | 파티션 단위 순서 보장 | 큐 단위로 메시지 순서 보장 |
| 처리 목적 | 대규모 데이터 스트림 (실시간 분석) | 트랜잭션성 메시징 (요청-응답, 워크큐 등) |
| 저장 방식 | 디스크 로그에 저장 (내구성 높음) | 메모리 중심 (빠름, 하지만 유실 가능성 있음) |
| 소비 방식 | Consumer Group 단위로 병렬 처리 | 각 큐에 한 소비자 그룹만 처리 |
| 사용 예 | 로그, 이벤트, IoT, 데이터 파이프라인 | 주문 처리, 이메일 발송, 알림 등 |