메세지 전송 전략
메시징 시스템의 메시지 보증 전략
- at-most-once (최대 한번만)
- 실패나 타임 아웃 등이 발생하면 메시지를 버릴 수 있다.
- 데이터가 일부 누락되더라도 영향이 없는 경우엔 대량 처리 혹은 실시간성이 중요한 서비스에 유용할 수 있다.
- at-least-once (적어도 한 번)
- 메시지가 최소 1번 이상 전달되는 것을 보장한다.
- 실패나 타임아웃 등이 발생하면 메시지를 다시 전송하며, 이 경우엔 동일한 메시지가 중복으로 처리될 수 있다.
- exactly-once (정확히 한 번)
- 메시지가 정확하게 한 번만 전달되는 것을 보장한다.
- 손실이나 중복 없이, 순서대로 메시지를 전송하는 것은 구현 난이도가 높고 비용이 많이 든다.
- exactly-once가 가장 이상적인 메시지 처리 방식이지만 난이도와 비용으로 인해 at-least-once로 타협하는 경우가 보편적이다. Kafka의 경우 at-least-once를 보장하며 일정 버전 이후에서만 옵션을 통해 exactly-once를 적용할 수 있다.
Kafka에서 적용
Producer ↔ Broker 간의 메세지 전송에서 Broker가 Producer로 수신한 메세지에 대해 응답하는 수준을 정할 수 있다.
- acks=0
- 이 수준은 자주 사용되지 않는다.
- 메시지 손실이 다소 있더라도 빠르게 메시지를 보내야 하는 경우에 사용된다.
- acks=1
- Leader가 메시지를 수신하면 ack를 보낸다.
- Leader가 Producer에게 ACK를 보낸 후 Follower가 복제하기 전에 Leader에 장애가 발생하면 메시지가 손실 된다.
- At most once(최대 한 번) 전송을 보장한다.
- acks=-1 : acks=all
- 메시지가 Leader가 모든 Replica까지 Commit되면 ack를 보낸다.
- Leader를 잃어도 데이터가 살아남을 수 있도록 보장한다. 그러나 대기 시간이 더 길고 특정 실패 사례에서 반복되는 데이터 발생 가능성이 있다.
- At least once(최소 한 번) 전송을 보장한다.
Kafka 메세지가 중복 처리 되는 경우
- Producer-Broker 간 ack 통신 실패
- Producer는 Broker에 메시지를 전송하고 ack를 수신 받는다.
- 만약 네트워크 상에서 ack가 소실/지연되어 수신 받는 데에 실패할 경우, Producer는 메시지 전송이 실패했다고 판단하여 재 전송 하게 되며 중복 메세지 발생.
- Consumer의 offset 갱신 실패
- Consumer가 메시지를 읽고 DB에 저장한 후에 offset을 갱신하기 전에 장애가 발생할 경우, Consumer는 재 시작 되었을 때 갱신 되지 않은 offset을 기준으로 메시지를 읽어오게 된다.
- 즉, 이미 DB에 저장된 메시지를 중복으로 가져오게 된다.
파티셔너를 통한 메세지 전송 전략
유니폼 스티키 파티셔너
- 파티셔너를 따로 설정하지 않았다면
UniformStickyPartitioner
로 설정. - 메세지에 키가 있다면?
- 메시지 키를 가진 레코드는 파티셔너에 의해서 특정한 해시 값이 생성.
- 이 해시 값을 기준으로 어떤 파티션에 저장될 지 결정된다.
(항상 동일한 파티션에 들어가는 것을 보장한다.)
- 메세지에 키가 없다면?
- 메시지 키가 없는 레코드는 라운드 로빈으로 파티션에 들어가게 된다.
- 프로듀서에서 배치로 모을 수 있는 최대한의 레코드들을 모아서 파티션으로 전송.
-
- 따라서 메시지 키가 없는 레코드는 파티션에 적절히 분배해서 들어간다.
Exactly-once 구현 방법
참고
- 프로듀서는 PID 0 / Seq 0을 브로커에게 보낸다.
- 브로커는 값을 저장하고 ACK를 보낸다.
- 프로듀서는 PID 0 / Seq 1을 브로커에게 보낸다.
- 브로커는 자신이 가지고 있는 Seq 0보다 1이 큰 값이 왔기 때문에 정상적으로 메세지가 온 것을 확인하고 ACK를 보내준다. 이 때, ACK는 유실된다.
- 프로듀서는 ACK를 받지 못했기 때문에 PID 0 / Seq 1을 브로커에게 다시 재 전송한다.
Kafka Transaction
- 중복 없는 메시지 전송을 위해 메시지를 비교하는 동작이 이루어지기 때문에 오버 헤드가 당연히 존재한다.
- 그러나 컨플루언트 블로그에서는 성능의 20%정도만 감소하는 것으로 전했다. 그러므로 일반적인 상황이라면, 중복 없는 전송을 설정하는 것이 좋다.
- 이 복잡한 전송 프로세스를 유지하기 위해 Kafka는 트랜잭션 API라고 부르는 프로세스를 가진다.
- 트랜잭션 : 데이터베이스와 같은 시스템에서 이루어지는 논리적인 작업 단위를 말하며, ACID를 성립하는 것을 목표로 한다.
- Atomicity: 원자성
- Consistency: 일관성
- Isolation: 독립성
- Durability: 영속성
- 트랜잭션 : 데이터베이스와 같은 시스템에서 이루어지는 논리적인 작업 단위를 말하며, ACID를 성립하는 것을 목표로 한다.
- Producer가 Broker로 exactly-once 방식으로 메시지를 전송할 때, 메시지는 전체 실행 또는 전체 실패로 처리된다. (트랜잭션의 성질 중 A에 해당하는 원자성).
- 이를 위해 서버 측에서 트랜잭션 코디네이터Transaction Coordinator가 프로듀서로부터 전송된 메시지를 관리하며, 커밋 또는 중단 등을 표시한다.
- 그리고 저장된 메시지들이 정상 커밋된 것인지 실패한 것인지 식별하기 위해 컨트롤 메시지라는 타입의 메시지가 추가로 사용된다.
- 이 컨트롤 메시지는 오직 브로커와 클라이언트 통신에서만 사용되며, 페이로드에 애플리케이션 데이터 즉, 메시지의 밸류를 포함하지 않아 애플리케이션에 노출되지 않는다.
어떻게 Exactly-once를 구현 할 수 있을까?
- Kafka는 at-least-once 방식을 지원했으나, 0.11.0.0 이상부터는 트랜잭션을 적용하여 exactly-once를 구현할 수 있다. Producer가 트랜잭션에서 처리한 데이터의 offset을 커밋 함으로써, Consumer에 정확하게 메시지를 전달할 수 있다.
- Producer side에서 트랜잭션을 적용하려면 Consumer side에서도 트랜잭션 기반으로 메시지를 읽어야 한다. 즉, Consumer 에도 트랜잭션 API를 적용해야 한다.
Kafka Producer exactly-once 동작 원리
- 프로듀서는 메세지를 보낼 때, PID(Producer ID) + 메세지 Seq.를 헤더에 포함해서 전송함.
- 프로듀서는 브로커의 ACK를 기다림.
- 브로커는 PID(Producer ID), 메세지 Seq.를 메모리에 기록하고 ACK함. PID, 메세지 Seq.는 replication 로그에도 저장됨.
- ⇒ replication 로그에 저장되기 때문에 브로커의 장애로 리더가 변경되는 일이 발생해도 새로운 리더가 PID와 시퀀스 번호를 정확히 알 수 있으므로 중복 없는 전송이 가능함.
- 브로커는 메세지를 받았을 때, 메세지 헤더의 Seq = 브로커가 가지고 있는 Seq + 1인 경우에만 메세지를 저장함.
- PID(Producer ID)는 브로커가 자동으로 생성해 줌. 개발자는 이것을 사용할 수 없음.
- 브로커 : 메세지를 받으면 프로듀서가 보낸 메세지를 열어서 PID와 Message Seq.를 확인한다.
- 브로커는 이 두 값을 메모리와 특정 토픽에 저장해두고 있는데, 프로듀서가 보낸 Message Seq = 브로커가 가진 Message Seq + 1 일 때만 메세지를 로그 파일로 저장한다.
- 그리고 브로커는 Message Seq 값을 갱신하고 ACK를 보내준다.
confluent-kafka-python에서 적용 방법
참고
https://smallrye.io/smallrye-reactive-messaging/3.22.1/kafka/transactions/
https://gunju-ko.github.io/kafka/2018/03/31/Kafka-Transaction.html
https://devfunny.tistory.com/788
https://jinyes-tistory.tistory.com/299
https://stackoverflow.com/questions/60876484/what-are-kafka-transactions
https://www.confluent.io/blog/transactions-apache-kafka
https://www.confluent.io/blog/exactly-once-semantics-are-possible-heres-how-apache-kafka-does-it/
https://yangbongsoo.tistory.com/77
https://m.blog.naver.com/ass1225/222035963333
- Producer와 Consumer 측에 트랜잭션을 적용해 Commit 하는 방식
- 트랜잭션이란?(데이터베이스)
- https://coding-factory.tistory.com/226
- 데이터 베이스 상태를 변환 시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위 또는 한꺼번에 모두 수행되어야 할 일련의 연산들을 의미한다.
- 트랜잭션은 데이터 베이스 시스템에서 병행 제어 및 회복 작업 시 처리되는 작업의 논리적 단위. 중간에 작업 시 실패한다면, 전체 작업을 실패로 처리해 데이터베이스의 무결성을 지켜줌.
- 원자성, 일관성, 독립성, 영속성 네 가지 성질을 가짐(ACID)
- 사용자가 시스템에 대한 서비스 요구 시 시스템이 응답하기 위한 상태 변환 과정의 작업 단위
- 하나의 트랜잭션은 commit 되거나 rollback 된다.
- Kafka에서 트랜잭션이란?
- producer부터 consumer까지 exactly-once를 보장하기 위한 옵션
- Kafka에서 정확히 한 번 전송은 중복 없는 전송 방식과 완전히 같은 개념은 아니며, 트랜잭션과 같은 전체적인 프로세스 처리를 의미함.
(중복 없는 전송은 정확히 한 번의 전송의 일부 기능이라고 할 수 있음) - 트랜잭션 API : 정확히 한번을 처리하는 별도의 프로세스
- Producer 트랜잭션 데이터를 여러 파티션으로 보낼 수 있으며 이러한 모든 쓰기가 커밋되거나 삭제되도록 보장합니다.
- Producer 트랜잭션 코디네이터
- 프로듀서에 의해 전송된 메세지를 관리, 커밋 또는 중단 등을 표시
- 트랜잭션 로그를 Kafka 내부 토픽인 __transaction_state에 저장
- 파티션 수와 레플리케이션 팩터 수가 존재, 브로커 설정을 통해 설정 가능
- 프로듀서가 이 토픽에 로그를 직접 기록하는 것이 아닌, 트랜잭션 코디네이터가 프로듀서로부터 받은 트랜잭션 관련 정보를 직접 기록.
- 프로듀서는 TRANSACTIONAL_ID_CONFIG 옵션을 실행하는 프로듀서 프로세스마다 고유한 아이디로 설정해야 함.
- Transaction Producer
- 다수의 파티션에 데이터를 저장할 경우 모든 데이터에 대해 동일한 원자성(atomic)을 만족 시키기 위해 사용됨
- 트랜잭션 프로듀서는 트랜잭션의 시작과 끝을 표현하기 위해 트랜잭션 레코드를 한 개 더 보냄
- 트랜잭션 컨슈머는 파티션에 저장된 트랜잭션 레코드를 보고 트랜잭션이 완료(commit) 되었음을 확인하고 데이터를 가져감.
- 어떻게 적용 가능?
- Producer 설정 시
- enable.idempotence=True 로 설정
- transactional.id=를 임의의 String으로 설정
- init_transaction() / begin_transaction(), commit_transaction()을 설정
- Consumer 설정 시
- isolation.level=”read-commited”로 설정해 사용 가능
- transaction.timeout.ms
- 트랜잭션 코디네이터가 진행 중인 트랜잭션을 사전에 중단하기 전에 Producer의 트랜잭션 상태 업데이트를 기다리는 최대 시간(ms)
- 이 값이 브로커의
transaction.max.timeout.ms
설정보다 크면 요청은InvalidTxnTimeoutException
오류 와 함께 실패
- transactional.id
- 트랜잭션으로 메세지 전달에 사용할 TransactionalId
- 클라이언트에선 이 id를 통해 새 트랜잭션이 시작되기 전에 해당 TransactionalId를 사용하는 트랜잭션은 완료되었음을 보장할 수 있으므로, Producer 세션 여러개에 걸친 신뢰도 있는 시맨틱스를 활용 가능
- TransactionalId가 제공되지 않으면 생산자는 멱등성(idempotent) 시맨틱스만 활용할 수 있음.
- TransactionalId가 구성된 경우
enable.idempotence
는 자동으로 활성화. - 기본적으로 TransactionId는 구성되지 않으므로 트랜잭션을 사용할 수 없음. 그리고트랜잭션에는 프로덕션에 권장되는 설정인 브로커가 3개 이상 있는 클러스터가 필요.
- 개발 환경에선 브로커 설정
transaction.state.log.replication.factor
을 조정하여 이를 변경할 수 있음.
- Producer 설정 시
기타 참고..
오늘은 헷갈릴까봐 대단원 별로 참고 링크를 따로 붙였습니다..
늘 좋은 글 써주셔서 감사합니다..!
https://velog.io/@hyun6ik/Apache-Kafka-Producer-Acks-Batch-Page-Cache-Flush
https://colevelup.tistory.com/24https://dzone.com/articles/kafka-consumer-delivery-semanticshttps://willseungh0.tistory.com/185
https://www.conduktor.io/kafka/delivery-semantics-for-kafka-consumers/
https://www.confluent.io/blog/exactly-once-semantics-are-possible-heres-how-apache-kafka-does-it/
https://wecandev.tistory.com/54
https://medium.com/@sdjemails/kafka-producer-delivery-semantics-be863c727d3f
https://www.baeldung.com/kafka-message-delivery-semantics
https://huisam.tistory.com/entry/kafka-message-semantics
https://velog.io/@jwpark06/장애에-대응하는-Kafka
https://dhkdn9192.github.io/apache-kafka/kakfa-exactly-once-delivery/
'Hadoop eco' 카테고리의 다른 글
[Kafka] 성능 측정 지표 (0) | 2023.05.23 |
---|---|
[Kafka] 기업 도입 사례들 (0) | 2023.05.23 |
[Kafka] Kafka 브로커의 동작 (0) | 2023.02.11 |
[Hadoop] 03. YARN(Yet Another Resource Negotiator) (0) | 2022.12.18 |
[발표 자료] Flink, 왜 써야 할까? (1) | 2022.12.01 |