https://youtu.be/7_VdIFH6M6Q?si=elt0JfJEcTO9i3tj
Event Driven Architecture / Stream Data pipeline
이벤트 또는 메시지 기반 메시지 전달의 신뢰성 확보하기.
- Exactly Once: 이벤트 발행 / 처리를 1회만 수행.
- At least Once: 장애 데이터가 중복으로 적재 / 처리될 수 있음.
- At most Once: 장애 등으로 데이터가 유실될 수 있으나, 중복은 발생하지 않음.
kafka 구조 간단 소개
- Producer: kafka 최소단위인 Record를 Broker로 전달
- 데이터가 제대로 전달되었는지 ACK로 확인
- Consumer: broker에 저장된 Record를 가져감.
- 데이터를 정상적으로 받았는지 Offset commit으로 전달.
- 데이터 담는 공간인 topic / partition.
- 정상적으로 데이터가 전달되었는지는 offset으로 확인함.
1. Producer의 메시지 전달 신뢰도 확보
Producer가 특정 topic 특정 partition으로 record 저장할 때, ACK를 받는다.
- 만약 ACK가 네트워크 장애로 유실될 경우.. 같은 데이터를 여러 번 저장할 수 있음
- 이 문제는 멱등성 프로듀서 - Idempotence producer 로 해결.
producer 옵션의 enable.idempotence=true 로 설정해준다.
- 3.0부터는 default true
- record를 전달할 때 PID(producer unique id) + Sequence Number를 같이 전달함.
- broker는 PID와 Seq 가지고 있다가 중복 적재요청이 오면, 이후에 들어온 건 적재하지 않는다.
이거 true 설정하면 acks=all이 활성화된다. kafka의 모든 리더/팔로워에 분산저장되었는지 확인해야 중복 방지가 되기 때문.
- 일반적으로 acks=all은 acks=0 (kafka 적재 확인하지 않음), acks=1(리더에 적재 확인) 보다 느리다.
- acks=1과 동일한 성능을 내려면
- max.in.flight.requests.per.connection=3 설정하고
- async producer 사용
- 의미: producer의 sender가 connection 개수만큼 병렬로 record를 전달 + ACK 응답을 동기로 받지 않음.
2. Topic to Topic의 메시지 전달
예컨대 광고시스템의 경우
- 사용자의 광고 클릭 로그를 받아서
- 광고주에게는 비용 청구를
- 매체사에게는 CPC 수익을 나누는 로직이
- Atomic하게 수행되어야 한다.
만약 광고 클릭로그를 수집해서 B topic으로 전달하는 앱에 문제가 생겨서 이벤트 중복이 발생한다?
- 광고주에게 비용이 두 번 청구되거나
- 매체사에게 CPC 수익을 두 번 전달하게 될 수 있음.ㅓ
즉, 두 개의 프로세스를 하나의 Transaction으로 묶어야 한다
- 광고 클릭로그를 topic에서 Consume + Offset commit 전달
- record를 B topic으로 전달
Transaction을 선언하고, 내부 로직에 Producer와 Consumer 로직을 동시에 정의한 모습.
- 특이점: 일반적으로는 Consumer가 commit offset 수행하지만, Producer에 transaction을 걸어두고 로직을 수행할 땐 producer가 sendOffsetsToTransaction() 메소드에서 commit offset까지 수행한다.
transaction이 commit되지 않으면, Consumer도 다음 작업을 수행하지 않도록 한다
- Consumer의 isolation level을 read_committed로 변경.
- default는 read_uncommitted. commit여부와 관계없이 topic에 올라온 record는 읽는다.
이렇게 만들면 처리속도에 문제는 없나? 라고 생각할 수 있는데, 외부자료나 내부테스트 결과에 따르면
- Producer: 대략 3% 낮은 성능.
- Consumer: 어차피 commit된 record만 읽는 거라 성능하락이 있진 않음
대신 commit완료까지는 기다려야 하기 때문에 End-to-End Latency는 증가할 수 있다.
3. Consumer의 중복 적재 방지
2번처럼 transaction으로 묶는 방식은 Topic to Topic인 경우에 해당함.
Consumer가 kafka topic 말고 다른 작업(i.e. DB로직) 을 수행할 경우 Transaction으로 묶기는 어렵다.
Transaction 말고 다른 방법으로 세 가지 제안.
Unique Key 활용한 멱등성 컨슈머.
- Oracle / Mysql 등의 DB
- Record Key를 DB unique Key로 지정.
Upsert 활용. 중간결과값인 Window Result도 DB에 저장.
Write-ahead log 활용
- transaction 만들기 전에 Log 기록 -> 적재 과정을 Atomic하게 관리.
- 어디까지 적재했는지를 WAL 파일로 관리함.
- Record Offset 관리 등이 필요해서 로직이 복잡해질 수 있다.
세 가지 다 적재성능은 큰 차이 없다.
정리
- idempotent Producer 활용
- Transaction Consumer / Producer
- DB저장할 때는 unique key / Upsert / WAL 등을 활용.
내부활용 사례
스마트 메시지 서비스
- 톡채널에 소재최적화, 타겟최적화 활용해서 적합한 광고를 소비자에게 전달하는 서비스
- kafka streams / MongoDB 사용.
이벤트 하나하나마다 실행하는 게 아니라, Tumbling window 단위로 묶어서 MongoDB에 저장함.
- 일정 주기마다 계산 -> 타겟팅 확인.
- MongoDB에 upsert 방식으로 저장. upsert에 활용할 unique key로는 Window key 사용함.
- kafka streams의 경우, commit할 때마다 window의 중간 기록이 output으로 나옴.
- 최종 window data만 원한다면 suppress 메소드 등이 필요하지만, 중간기록을 upsert로 저장하고 최종기록도 upsert로 저장할 수도 있음
마무리
- 위 모든 예시는 자바 기준, 다른 언어와 라이브러리에서는 확인 필요
- 메시지 전달 신뢰성 테스트는 재현이 까다롭다.
- topic to topic의 경우 단일 kafka cluster를 상정했음. 서로 다른 kafka cluster 간 연동에는 다른 관점이 필요하다.