05 데이터 정합성 - kafka
05 데이터 정합성 - kafka
1. Note
- 전반적으로 내용의 반복,
- 이벤트 방식으로 진행하는거고, 반드시 카프카는 아니여도됨.
- 카프카를 기준으로 비슷한 패턴들을 검토하면 될 듯.
1. Kafka
1. Kafka
- 대용량 데이터를 실시간으로 안정적으로 전달하기 위한 분산 메시지 시스템(Message Broker)
- Event-Driven Architecture(EDA) 환경에서 서비스 간 이벤트를 비동기적으로 전달하는 역할
2. Kafka 흐름
전체적인 흐름
1 2 3 4 5 6 7 8 9
Producer ↓ 이벤트 발행 Kafka Topic 저장 ↓ Consumer 메시지 소비 ↓ 비즈니스 로직 수행 ↓ 필요 시 새로운 Event 발행
Order
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
Order Service (Producer) ↓ OrderCreated Event 발행 ↓ (Kafka) order-topic 저장 ↓ Payment Consumer Group ├─ Consumer 1 └─ Consumer 2 ↓ 메시지 중 하나가 소비 ↓ 결제 처리 ↓ PaymentCompleted Event 발행 ↓ (Kafka) payment-topic 저장 ↓ Inventory Consumer Group ↓ 재고 처리
2. Kafka 주요 구성요소
1. Broker
- Kafka 서버(노드)를 의미하며, 메시지를 저장하고 관리하는 역할
- Producer가 전송한 메시지를 저장하고, Consumer가 이를 읽을 수 있도록 제공
- 여러 Broker를 클러스터 형태로 구성하여 대용량 처리 및 장애 대응이 가능하도록 설계
- 위치
1 2 3 4 5
Producer ↓ Broker ↓ Consumer
2. Topic
- 동일한 종류의 메시지를 구분하여 저장하는 논리적 공간
- 메시지를 분류하는 카테고리 역할
예시
1 2 3 4
#도메인 별로 토픽을 구성 OrderCreated event는 -> order-topic 저장 PaymentCreated event -> payment-topic 저장 inventoryCreate evenet -> inventory-topic 저장
분류
1 2 3 4 5 6 7 8 9 10 11 12 13
# 방법 1) 토픽안에 같은 도메인 내 여러 이벤트를 저장 order-topic ├─ OrderCreated 주문 성공 ├─ OrderCanceled 주문 취소 ├─ OrderCompleted 주문완료 └─ OrderFailed 주문 실패 # 방법 2) 동일한 이벤트에 타입을 주고 서비스단에서 구분해서 처리 OrderCreated event { "eventType": "OrderCreated", # 이벤트 내에 타입으로 분류 "orderId": 1 }
3. Producer
- 이벤트(Message)를 Kafka Topic으로 발행(Publish)하는 주체
- 외부에 있음!
- 이벤트를 생성하여 Kafka에 전달하는 역할
- 특정 Topic 선택 가능
- 필요 시 Key를 이용하여 메시지 분산 처리 가능
- 데이터 작업
역할
1 2 3 4 5 6 7
Order Service ↓ OrderCreated Event 생성 ↓ order-topic 전송 # 여기까지가 프로듀서의 역할 ↓ 카프카(브로커)에서 이벤트 분류 및 관리
4. Consumer
- Kafka Topic의 메시지를 읽어(Consume) 처리하는 주체
- Consumer가 지속적으로 가져가는(Pull) 방식
Consumer가 지속적으로 Broker에게 요청을보내서 데이터의 유무를 확인함.Broker가 이벤트 발생마다 데이터를 Push 하는 구조가 X
- Producer가 발행한 이벤트를 소비하여 필요한 비즈니스 로직을 수행하는 역할
- 필요 시 새로운 이벤트 발행 가능
흐름
1 2 3 4 5 6 7
Payment Service ↓ order-topic 구독 ↓ OrderCreated Event 소비 ↓ 결제 처리
5. Consumer Group
- 여러 Consumer를 하나의 그룹으로 묶어 메시지를 분산 처리하는 구조
- Consumer Group 내부에서 메시지를 자동으로 분배하여 처리
- 토픽에 구성된 파티션은
- 컨슈머별로 1개 이상의 파티션으로 매칭됨
- 그래서 컨슈머 별로 병렬 처리가 가능해짐
- Note
- Consumer Group은 특정 Topic을 처리하는 Consumer들의 묶음이며,
- 하나의 Topic은 여러 Group이 독립적으로 접근하여 각자의 목적에 맞게 메시지를 소비
구조
1 2 3 4
payment-group ├─ consumer-1 ├─ consumer-2 └─ consumer-3
3. Kafka 내부 개념
1. Partition
- Topic 내부에서 메시지를 분산 저장하는 단위
- Kafka는 하나의 Topic을 여러 Partition으로 나누어 메시지를 병렬 처리할 수 있도록 구성
- 병렬로 구성되어있어서 컨슈머그룹의 여러 컨슈머가 동시에 처리가 가능해짐
- 데이터의 순서
- 파티션 내부에서는 보장하지만
- P0, P1, P2 간에 순서는 보장하지 않음. (병렬처리)
구성
1 2 3 4
order-topic ├─ Partition 0 -> 주문1, 주문4, 주문7 ├─ Partition 1 -> 주문2, 주문5, 주문8 └─ Partition 2 -> 주문3, 주문6, 주문9
2. Offset
- Partition 내부 메시지의 고유한 순번(위치 정보)
메시지가 저장될 때마다 순차적인 번호를 부여
1 2 3 4 5 6
Partition 0 Offset 0 -> OrderCreated Offset 1 -> PaymentCompleted Offset 2 -> InventoryUpdated Offset 3 -> OrderCompleted
- 사용
- Consumer Group이 어디까지 메시지를 읽었는지 관리하기 위해 사용
- Consumer 장애 발생 시 마지막 Offset 이후부터 재처리 가능
1 2 3 4 5 6
Partition 0 Offset 0 -> OrderCreated -> 완료 Offset 1 -> PaymentCompleted -> 완료 Offset 2 -> InventoryUpdated -> 진행중 Offset 3 -> OrderCompleted -> 다음 처리 대상
3. Replication
- Partition 데이터를 복제하여 장애 상황에 대비하는 기능
- 특정 Broker 장애 발생 시 데이터 유실을 최소화하기 위해 사용
Case
1 2 3 4 5 6 7 8
Broker 1 └─ Partition 0 (원본) Broker 2 └─ Partition 0 (복제본) Broker 3 └─ Partition 0 (복제본)
4. 메시지 처리 방법
1. Note
- 메시지를 어떤 보장 수준(Guarantee)으로 처리할 것인지에 대한 부분
- 비교표
| 방식 | 특징 | 위험 | 구현 주체 |
|---|---|---|---|
| at-most-once | 최대 1회 처리 | 메시지 유실 가능 | Consumer 구현 (Offset 선 Commit) |
| at-least-once | 최소 1회 처리 | 중복 처리 가능 | Consumer 구현 (처리 후 Commit) |
| exactly-once | 정확히 1회 처리 | 구현 복잡 / 성능 비용 | Kafka 기능 + Consumer 구현 |
2. at-most-once
- 메시지를 최대 한 번만 처리하는 방식
중복은 없지만 메시지 유실 가능성이 존재흐름
1 2 3 4 5 6 7
메시지 수신 ↓ Offset 먼저 Commit ↓ Consumer 장애 발생 ↓ 메시지 처리 실패 # 이미 읽었다고 판단하여 재처리하지 않음
3. at-least-once
- 메시지를 최소 한 번 이상 처리하는 방식
- 데이터 유실 방지에 집중
- 같은 메시지가 중복 처리될 수 있음
흐름
1 2 3 4 5 6 7
메시지 처리 ↓ Consumer 장애 발생 ↓ Offset Commit 실패 ↓ 재시작 후 다시 처리
4. exactly-once
- 메시지를 정확히 한 번만 처리하는 방식
- 중복도 없고 유실도 최소화
- 구현 복잡하고 성능 비용 존재함.
흐름
1 2 3 4 5
메시지 처리 ↓ Transaction 보장 ↓ 한 번만 Commit
5. 장애 처리
1. Retry
- 메시지 처리 실패 시 다시 처리하는 방식
- 일시적인 장애 상황(네트워크, 외부 API 오류 등)에 대응하기 위해 사용
흐름
1 2 3 4 5 6 7
OrderCreated Event ↓ Payment Service 처리 실패 ↓ Retry(Payment Service에서 일정시간 후 재시도 요청) ↓ 재처리 성공
- 점검 포인트
- 일시적 장애 복구 가능
- 재시도 횟수 제한 필요
- 무한 Retry 시 장애 전파 가능
2. DLQ (Dead Letter Queue)
- Retry 이후에도 처리하지 못한 메시지를 별도로 저장하는 공간
- 장애 메시지를 분리하여 서비스 전체 장애를 방지하기 위해 사용
흐름
1 2 3 4 5 6 7
OrderCreated Event ↓ Retry 3회 실패 ↓ DLQ 저장 # 정상 메시지 흐름을 막지 않고 실패 메시지만 격리 ↓ 운영자 확인 / 수동 처리
- 점검 포인트
- 장애 메시지 추적 가능
- 서비스 전체 중단 방지
- 운영 관점에서 중요
3. Consumer 장애
- Consumer 장애 발생 시 Partition을 다른 Consumer에게 재할당(Rebalance)
- 흐름
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 정상적인 상황
payment-group
├─ consumer-A → P0
├─ consumer-B → P1
└─ consumer-C → P2
consumer-C 장애 발생
↓
Rebalance # 카프카에서 작업함
↓
consumer-A → P0 + P2 #C의 파티션을 consumer-A에 할당
consumer-B → P1
#추가적으로
Offset 정보를 기반으로 마지막 처리 위치 이후부터 재처리
Consumer 장애가 발생하더라도 서비스가 지속될 수 있도록 설계됨
6. Kafka와 데이터 정합성
1. Eventual Consistency(최종 일관성)
- Kafka 기반 비동기 구조에서는 즉시 데이터 정합성을 보장하기 어려움
- 일정 시간이 지나면 최종적으로 데이터가 일관된 상태에 도달하는 방식
흐름
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
주문 생성 ↓ 주문 서비스 완료 ↓ 결제 서비스 처리 중 ↓ 재고 서비스 처리 중 (일시적으로 데이터 상태 차이 발생) ↓ 일정 시간 후 주문 완료 결제 완료 재고 차감 완료 # 최종적으로 동일함
2. 순서 보장 이슈
- Partition 내부에서만 순서를 보장됨
1
2
3
4
5
6
7
8
9
10
# 파티션 내부에서의 흐름은 보장됨.
P0 → 1번 이벤트 → 2번 이벤트 → 3번 이벤트
# 파티션끼리는 흐름이 보장이 X
# 실행된 스레드마다 작업속도나 상황이 다르기 때문에
# 종료되는 순서가 다르거나, 역전되는 상황이 발생할 수 있음
# 재고/결제 같은 민감 영역은 별도 보호 필요
P1 → 1번 이벤트 → 4번 이벤트
P3 → 2번 이벤트 → 5번 이벤트
P4 → 3번 이벤트 → 6번 이벤트
3. 중복 메시지 고려
- 장애 상황에서 동일 메시지가 다시 처리될 수 있음 (at-least-once 기반)
흐름
1 2 3 4 5 6 7 8 9
결제 완료 ↓ Offset Commit 전 Consumer 장애 ↓ 재시작 후 동일 메시지 재처리 # 최종적으로 결제 2번 실행 # 중복 결제 위험 # 멱등성(Idempotency) 처리가 필요
7. Spring&Kafka
1. 셋팅
1. 의존성
1
2
3
implementation 'org.springframework.kafka:spring-kafka'
// Spring Kafka 라이브러리
// Producer / Consumer 기능 제공
2. properties
1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
kafka:
bootstrap-servers: localhost:9092
# Kafka Broker 주소
consumer:
group-id: order-group
# Consumer Group 이름
auto-offset-reset: earliest
# Offset 없을 경우 시작 위치
# earliest : 처음부터 읽기
# latest : 최신 메시지부터 읽기
3. Config
1
2
3
4
5
6
@Configuration
@EnableKafka
// Kafka 기능 활성화
public class KafkaConfig {
}
2. Producer Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
@RequiredArgsConstructor
public class OrderProducer {
private final KafkaTemplate<String, Object> kafkaTemplate;
// Kafka 메시지 발행 객체
public void sendOrder(OrderEvent event) {
kafkaTemplate.send(
"order-topic",
event
);
// 특정 Topic으로 메시지 발행
}
}
3. Consumer Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class OrderConsumer {
// @KafkaListener가 내부적으로 계속 polling처리해서 별도 실행X
@KafkaListener(
topics = "order-topic",
groupId = "payment-group"
)
// order-topic 구독
// payment-group Consumer Group 사용
public void consume(OrderEvent event) {
System.out.println(event);
// 메시지 소비 후 비즈니스 로직 처리
}
}
This post is licensed under CC BY 4.0 by the author.