개요
카프카에서 신뢰성을 보장한다는 것은 크게 두 가지로 나뉜다
- 최소 한번 전달하기 : 매개변수 구성과 시스템 설계의 Best Practices 활용
- 정확히 한번 전달하기 : 멱등성 프로듀서와 트랜잭션의 조합
01. 멱등성 프로듀서
Concept
- idempotency : 멱등성. 멱등성 자체는 동일한 작업을 여러번 실행해도 한 번만 실행한 것과 결과가 같은 것을 의미한다.
멱등성 프로듀서 작동원리
NOTE모든 메시지가 고유한 PID와 sequence id를 가지고 있어야 한다
- 각 프로듀서가 고유한 PID를 할당받음(Producer가 초기화될 때 브로커로부터 발급됨)
- 프로듀서는 각 메시지에 순차적인 sequence Id를 할당함
중복 검사 과정
- Producer가 메시지를 보낼 때 (Topic, Partition, PID, sequence id) 를 식별자로 사용
- 브로커는 이 정보를 사용하여 중복 메시지를 감지
- 동일한 식별자( Topic, Partition, PID, sequence id) 로 메시지를 받으면 브로커는 적절한 에러를 반환
브로커가 예상보다 높은 sequence id를 받으면 out-of-order sequence error를 반환
Concept
- max.in.flights.requests.per.connection : 프로듀서가 브로커로 보낼 수 있는 최대 요청 수 . 브로커는 할당된 모든 파티션에 쓰여진 5개 메시지를 추적하기 위해 고유 식별자를 사용한다.
멱등성 프로듀서의 한계
멱등성 프로듀서는 프로듀서의 내부 로직으로 인한 재시도를 통해 생기는 중복만 방지한다. 만약 동일한 메시지를 가지고
producer.send()를 두 번 호출하면 멱등성 프로듀서가 개입하지 않는만금 중복된 메시지가 생길 수 있다. 즉 멱등성 프로듀서는 프로듀서 자체의 재시도 매커니즘으로 인한 중복만 방지한다.
02. 트랜잭션
NOTE트랜잭션이 정확히 한번 처리를 보장하는 방식
- Tranasactional Producer를 활용한 atomic multipartition write에 기반한다.
- 기본적인 아이디어는 오프셋을 커밋하는 것과 결과를 쓰는 것은 둘 다 메시지를 파티션에 쓰는 것을 수반한다 는 것에 기반.
Concept
- atomic multipartition write : 여러 파티션에 거친 쓰기 작업을 원자적으로 수행하는 것
- transactional producer : transactional.id 설정이 잡혀있고 ininitTransactions()
- zombie fencing : 어플리케이션의 좀비 인스턴스가 중복 프로듀서를 생성하는 것을 방지하기 위한 메커니즘. epoch라는 개념을 사용하여 좀비 프로듀서를 식별한다. 같은 transactional.id를 가진 프로듀서가 새로운 epoch를 가지면 이전 epoch의 프로듀서는 좀비 프로듀서로 간주한다.
- isolation.level : 트랜잭션이 진행 중일 때 다른 컨슈머가 해당 메시지를 읽을 수 있는 수준을 정의한다. read uncommitted, read committed 두 가지 격리 수준을 제공. read committed는 커밋된 트랜잭션의 메시지만 읽을 수 있다. read uncommitted는 트랜잭션의 상태와 관계없이 모든 메시지를 읽을 수 있다.
isolation.level
트랜잭션이 진행 중일 때 다른 컨슈머가 해당 메시지를 읽을 수 있는 수준
read uncommitted : 트랜잭션의 상태와 관계없이 모든 메시지를 읽을 수 있다. read committed : 커밋된 트랜잭션의 메시지만 읽을 수 있다.
- Read Committed를 사용할 때의 고려사항:
메시지 지연: 트랜잭션이 커밋될 때까지 대기 필요 추가적인 메모리 사용: 트랜잭션 상태 추적에 필요 처리량 감소: 트랜잭션 상태 확인에 따른 오버헤드
- Read Uncommitted를 사용할 때의 고려사항:
빠른 처리: 즉시 메시지 처리 가능 낮은 오버헤드: 트랜잭션 상태 확인 불필요 데이터 일관성 위험: 롤백될 수 있는 데이터 처리 가능성
결국은 가용성과 일관성 사이의 trade-off
트랜잭션의 동작방식
Chandy-Lamport 알고리즘
기본적인 목적은 여러 노드가 독립적으로 동작하고 메시지를 주고받는 상황에서 **특정 시점의 전체 시스템 상태를 캡처(파악)**하는 것이다.
이는 분산 스냅샷 문제이며 카프카는 이를 위해 Tranaction Marker라는 특수한 메시지를 사용한다.
2PC(2 Phase Commit)
Concept
- 2PC : 2 Phase Commit. 분산시스템에서 모든 참여자의 트랜잭션의 원자성을 보장하기 위한 합의 프로토콜의 일종
준비단계
- 모든 참여자들이 트랜잭션을 완료할 수 있는지 확인하는 단계
- 참여자들은 이 단계에서 필요 리소스에 대한 락을 획득
커밋단계
- 실제 트랜잭션 실행
- 준비단계에서 하나라도 부정적인 응답이 오면 모든 참여자는 롤백
트랜잭션의 한계
트랜잭션으로 해결할 수 없는 문제를 다룬다.
-
스트림 처리 어플리케이션에서 외부 효과를 발생시키는 작업
-
카프카 토픽에서 읽어서 DB에 쓰는 경우
-
데이터베이스에서 읽어서 카프카에 쓰고, 여기서 다시 다른 DB에 쓰는 경우
-
한 클러스터에서 다른 클러스터로 데이터를 복제하는 경우
-
발행 구독 패턴
- 메시지를 쓰고 나서 커밋하기 전에 다른 어플리케이션이 응답하기를 기다리는 패턴은 반드시 피해야 한다. 다른 어플리케이션은 트랜잭션이 커밋될 때까지 메시지를 받지 못할 것이기 때문에 결과적으로 데드락이 발생할 수 있다.
03. 트랜잭션 성능
-
트랜잭션 초기화와 커밋 요청은 동기적으로 동작하기 때문에 성공적으로 완료하거나 실패하거난 타임아웋 하거나 할 때 까지 어떤 데이터도 전송되지 않는다. 따라서 오버헤드는 더 증가한다.
-
프로듀서에 있어서 트랜잭션 오버헤드는 트랜잭션에 포함된 메시지의 수와는 무관하다. 따라서 트랜잭션마다 많은 수의 메시지를 넣는 것이 상대적으로 오버헤드가 적다.
-
트랜잭션 기능이 컨슈머 성능에 미치는 핵심적인 영향은 read_commited consumer mode에서는 아직 완료되지 않는 트랜잭션의 레코드들이 리턴되지 않는다는 것이다. 트랜잭션 커밋 사이의 간격이 길어질 수록 컨슈머는 메시지가 반환될 때까지 더 오래 기다려야 한다.
Takeaway
Key Takeaway
- 리더 레플리카는 새 메시지가 쓰여질 때마다 인 메모리 프로듀서 상태에 저장된 최근 5개 sequence id를 업데이트한다. 팔로워 레플리카는 메시지를 복제할 때마다 자체적인 인메모리 버퍼를 업데이트한다.
- 멱등성 프로듀서는 프로듀서의 내부 로직으로 인한 재시도를 통해 생기는 중복만 방지한다. 만약 동일한 메시지를 가지고
producer.send()를 두 번 호출하면 멱등성 프로듀서가 개입하지 않는만금 중복된 메시지가 생길 수 있다. 즉 멱등성 프로듀서는 프로듀서 자체의 재시도 매커니즘으로 인한 중복만 방지한다. - 트랜잭션은 근본적인 매커니즘을 뜻한다. 카프카 스트림즈는 멱등성을 구현하기 위해 트랜잭션 기능을 사용한다. 스파크 스트리밍이나 플링크와 같은 다른 스트림 처리 프레임워크의 경우 사용자에게 멱등성을 제공하기 위해 다른 매커니즘을 사용한다
- 트랜잭션은 대부분 프로듀서 쪽 기능이다. 컨슈머에 올바른 isolation level이 설정되지 않을 경우 멱등성을
- 트랜잭션이 정확히 한번 처리된다는 것은 기본적으로 읽기, 처리, 쓰기 작업이 원자적으로 이루어진다는 것을 의미한다.
- atomic 하다는 것은 기본적으로 모든 작업이 성공하거나 아니면 모든 작업이 실패한다는 것을 의미한다. 카프카에서는 트랜잭션을 사용하여 이러한 atomicity를 보장한다.
- 메시지를 쓰고 나서 커밋하기 전에 다른 어플리케이션이 응답하기를 기다리는 패턴은 반드시 피해야 한다. 다른 어플리케이션은 트랜잭션이 커밋될 때까지 메시지를 받지 못할 것이기 때문에 결과적으로 데드락이 발생할 수 있다.
Reference
- 카프카 핵심 가이드