우아콘 2023 - Kafka를 활용한 이벤트 기반 아키텍처 구축
https://youtu.be/DY3sUeGu74M?si=tDHw0pmczIcrHuLQ
- 딜리버리시스템 임준수
- 배차담당시스템 송인태
왜 적용했는가?
두 시스템이 하는 일: '배달을 잘 한다'
- 배달기능이 늘어가면서 복잡도 증가.
- 알림: 배달상황이 변경됐을 때 (ex. 라이더가 음식 픽업했을 때) 제공
- 배달시간
- 통계: 배달상황이나 지표 추출을 위한 기능
- 쿠폰: 배달 지연됐을 때 고객에게 제공
- 배달이 아닌 기능을 분리할 필요가 생김.
- 하지만, 배달에 관련된 다양한 기능은 배달에 엮여서 잘 동작해야 함.
배달이 변경되었을 때, 관련 기능이 '동시에' 변경될 필요가 없는 것들이 있다. (Eventual Consistency)
- 배달 '이벤트'가 발생하면, 나중에라도 관련 기능이 동작하면 됨.
배달은 배달만 잘 수행하고, 이벤트를 발행한다. 나머지 기능은 이벤트를 보고 처리한다.
이벤트의 구성 요소
도메인 이벤트: 도메인에 영향을 주는 관심 정보. 마틴 파울러
- 필요한 정보: 대상, 발생시간, 행동.
배달정보를 전달하기 위한 이벤트 구성요소들
- 대상: 고유 식별자 정보 (ex. '배달이' A라이더에게 11시에 배차되었다.)
- 행동: 이미 벌어진 사건이므로 과거형으로 표현 (ex. 배달이 A라이더에게 11시에 '배차되었다')
- 정보: 행위와 관련된 값. (ex. 배달이 'A라이더에게' 11시에 배치되었다.) 필요시 행위 외에 다른 값 추가 가능.
- 배민의 경우 '주문'이 기본 단위. 배달이벤트에도 주문 식별자를 넣어서 전달함.
- 시간: 행위가 발생한 시간 (이벤트 발행시간이 아님) (ex. 배달이 A라이더에게 11시에 배차되었다)
이 정의를 충족하면, 다양한 도메인의 이벤트에 적용할 수 있다.
아키텍처 구축의 장점
복잡도가 낮아진다. 각 컴포넌트는 본인 로직에만 충실하고, 이벤트를 발행하면 된다.
- 요구사항에도 대응하기 편해진다.
이벤트 소비처에서도 결합도가 낮아진다.
- 이벤트 내에 정보가 많으므로, 배달정보 상세조회하려고 API 별도로 호출할 필요가 없음.
데이터분석 관점에서의 장점
- 언제 배달이 생겨서, 누구에게 전달되고 언제 배달이 완료되었는지 파악 가능.
유의점
소비하는 쪽에서 데이터 요구한다고 무분별하게 추가해줘선 안 된다.
- 소비처와의 결합도를 높이는 결과.
- 배민에서는 데이터 추가 기준을 '행위자'로 정의해서, 데이터 필드가 불필요하게 많아지는 문제를 피하려고 함.
배달순서가 매우 중요함. 예컨대 사용자는 주문생성 -> 주문취소를 수행했는데 이벤트 순서 문제로 주문취소 -> 주문생성 순으로 들어온 경우.
이벤트 파이프라인: Kafka
AWS SQS + SNS와 kafka 중에서 kafka를 선택.
- 순서보장
- 고성능 / 고가용성
- 통합 도구 / 전담팀 지원
kafka topic의 partition 활용, key별로 순서 보장
- 배달의 경우 배달번호 기반으로 이벤트 순서 보장.
- kafka의 Partition 증설, batch 발행, page Cache와 같은 기능... 고성능 보장.
- broker를 cluster 단위로 관리... broker 한 대에 문제 생겨도 대응 가능하도록 고가용성 보장.
통합 도구: kafka streams, kafka connect 등... 시스템 개선, 확장 가능.
운영 시 발생했던 문제점과 해결방안
kafka가 고가용성을 보장하지만, 순단이 없는 건 아님.
- EBS volume 이슈, zookeeper와의 통신 이슈, 네트워크 이슈 등... 의 문제
- 이벤트 발행 실패
- 재시도 처리하면서 이벤트 순서가 변경됨
- 즉 도메인 상태 != 이벤트 발행 결과. -> 서비스 장애 발생.
Transactional Outbox Pattern
- DB의 transaction을 활용해서 이벤트를 outbox table에 정의
- 발행할 이벤트를 도메인 처리 txn과 묶어서 outbox 테이블에 저장함.
- message relay가 이벤트 발행을 보장하는 기법.
- outbox 테이블에 저장된 이벤트를 순서대로 읽어서 발행.
Message Relay 구현에 고려해야 할 점: 저비용, 안정성, 처리량
- 직접 구현하지는 않았고, 오픈소스 debezium 사용.
debezium: DBMS에서 발생하는 변경사항을 감지하고, 타 시스템에 전송해주는 오픈소스.
- CDC에서 쓰이는 거라 보면 됨.
- 저비용: kafka connect 제공됨. 설정만 잘 하면 kafka connect 등록 + 실행, 모니터링 기능 제공.
- 모니터링의 경우 kafka Connect 데이터만 추가로 확인하면 됐음
- 안정성: DB 변경 시 발생하는 binary log을 순서대로 읽음 -> 이벤트 순서 보장. topic offset으로 이벤트 발행 보장.
- kafka 순단 발생? -> 발행 실패한 offset부터 재시도.
- 처리량: outbox의 테이블 파티셔닝 활용 가능
- 일반적으로 kafka connect는 task 수를 조절해서 처리량을 늘릴 수 있음. 그러나 mysql debezium connector는 task 수를 1개로 고정해야 함.
- binary log을 순서대로 읽어야 하다보니 connector 개수를 1로 고정한 것 같음.
- 따라서 outbox table을 n개 만들고, producer가 event key 기반으로 이벤트 파티셔닝 -> 적절한 outbox table에 저장.
- 각각의 outbox table에 connector 붙여서 이벤트 발행... scalable 구조 구현
- 일반적으로 kafka connect는 task 수를 조절해서 처리량을 늘릴 수 있음. 그러나 mysql debezium connector는 task 수를 1개로 고정해야 함.
이벤트 활용 사례
순서가 보장된 이벤트는 이벤트 스트림을 구성할 수 있음. 이벤트 스트림을 서비스 개선에 활용했다.
CQRS 도입
- 서비스 규모가 커지면서, 대량의 데이터 조회가 쉽지 않아짐.
- 하나의 모델로 query / command 처리중... 전체 시스템 장애 발생.
이벤트 스트림 기반으로 query 모델 구축, 조회 기능은 query 모델을 사용하도록 수정.
이슈대응, 모니터링, 성과분석을 위한 데이터분석 환경...
- kafka Stream에 S3 Sink Connector 붙여서 S3에 저장.
- AWS의 Glue / Athena로 데이터분석 시스템 구축
- Airflow로 전사 DataLake에 연동 -> 배달데이터를 다른 팀에서도 확인할 수 있도록 함.
실시간으로 배달 / 라이더 관측해야 하는 니즈가 있었음
- 특정 시간, 특정 지역에 현재 배달 건수와 라이더 수 집계
- 그전에는 주기적으로 batch 돌려서 했는데, DB 부하도 크고 실시간으로 데이터 반영하지 못한다는 문제점이 있음.
- kafka Streams이 구현되어 있었기 때문에, 애플리케이션으로 올려서 해결.