created at 2025-04-25
Table of contents
Notation
- PEL (Pending Entry List): Redis Stream에서 이벤트를 소비(XREADGROUP)했지만 아직 ACK되지 않은 상태의 이벤트 목록.
- Rebalancing: 특정 파드 또는 컨슈머가 중단되었을 때, 해당 컨슈머가 담당하던 이벤트 또는 파티션을 다른 컨슈머에게 할당하는 과정.
redis stream 은 일단 단일 파티션이며 해당 파티션 listening 하는 consumer group 중 누군가 죽으면 두 가지를 신경써줘야함.
- 해당 컨슈머가 읽다가 죽었을 경우 PEL 에 남아있는 이벤트를 누군가가 대신 처리해주어야함.
- 파티션을 재할당시켜주는것. redis XREADGROUP 쓰면 재할당 굳이 해줄 필요없이 자동가능. redis 에서 XREADGROUP 컨슈머 그룹 별 offset 저장/관리해주기때문. 컨슈머 +/- 에 상관없이 누락되는 이벤트는 없음.
How can I gracefully re-consume for my unconsumed event in PEL?
- stream-key 는 postfix/prefix 로 version 추가. 그래서 특정 토픽 처리 방식이 변경되었을 때 consumer 들의 처리방식의 일관성을 유지하도록.
- consuming 이후 redis stream 으로 ACK 안날리면 이벤트는 PEL(pending entry list) 에 머무름. 이 때 consumer serving 횟수, idle time(서빙 이후 경과 시간)도 볼 수 있음. PEL 에서 idle time 이 일정시간이상이면 이벤트 소비 실패 처리로 판단. 이후 XCLAIM, XAUTOCLAIM 으로 consume.
일단 처리 실패한 이벤트가 무엇인지 정의가 필요함. PEL에 존재하면서, idleTime
이 일정 기준(x
ms) 이상 지난 이벤트를 실패로 간주. 실패횟수도 일정이상 초과하면 더 이상 재처리의 의미가 없다고 판단하고 그 빈도를 낮추기 위해 별도의 DLQ(Dead Letter Queue) 로 이동시킴.
어떤 파드가 어떤 이벤트를 처리할것인가?
이벤트 별 처리( event-A -> pod-1, event-B -> pod-2 )
- x초 간격 pod 들은
alive
redis set 에 자신의 고유키를 등록함. TTL y초 - pod 는 현재 live 한 node 들의 list 를 알고 있음. XPENDING event ID 가져와서 처리안된상태 확인하고, 내 키를 alive 에 다시 push 하고,
min(hash(msgId + pod-1-key), hash(msgId + pod-2-key), ...)
가 제일 작은 애가 해당 이벤트 처리하는거임. 물론 이 때 pod 들의 실행시점은 동기화되어있지 않아 alive 리스트는 변동가능이므로 여러 파드가 동시에 이벤트를 읽을려고 할 수 있음. 동시 처리제어가 필요한데 consume 이전에 lock 걸고 끝나면 ACK 전송 후 lock 해제하면 PEL 내 소비중인 msg 재접근를 차단가능 및 동시접근 제어가능. 이벤트 소비가 (lock -> consume -> business logic -> ack -> unlock) 이 시퀀스로 진행되기때문에 해당 이벤트는 중복소비가 불가능.
요약하면 PEL 내 이벤트의 중복소비를 제어하는 방식은 아래와 같이 구현함.
- 이벤트 소비 끝나면 ack 로 PEL 에서 제거.
- 이벤트 소비 전/후 lock/unlock.
- 이벤트 소비 하기 전 msgId lock waitTime=0 시도 후 누군가 점유하고있으면(다른 pod 가 해당 msg 처리중이면) 다른 msg 로 skip.
롤백이 필요하면 소비 실패 시 롤백 진행필요.
이벤트 consume 했는데 ack 못날리고 죽으면? 이벤트 처리기록 저장 없이 죽으면? 로직에 따라 롤백필요! 내부 호출은 TX 로 묶으면 간단하게 롤백 가능. 하지만 외부 호출 중간에 껴있는 경우는 롤백요청을 직접 한번 날려야함. 예로 페이코 포인트 사용 했는데 롤백되버리면 중복 페이코 포인트사용 호출을 제어할 수 있어야 함.
- 이벤트 처리할 때 lock 획득 후 처리할 수 있도록. ex) pod-a 가 처리중이고 아직 자신의 alive 를 등록못했는데 pod-b 에서 자신이 처리진행할 수 있음. 동시 이벤트 처리 제어 해야함.
특정 노드가 이벤트 처리( if(10:12:00 000000 KST < now <= 10:15:00 000000 KST) event-A,B,C,… -> pod-1 )
- pod-a -> leader key 존재 확인 -> pod-b -> leader key 존재 확인 -> pod-a SETNX leader “{nodeId}” + TTL 선점 시도. 1 반환 시 내가 리더니까 이벤트 청크들 소비 시작. 0 반환받으면 나는 leader 가 아님. next.
- leader key 가 존재하면 내부 nodeId 확인 후 나와 동일하다면 나는 리더파드이며 PEXPIRE 로 주기적으로 leader key 의 TTL 늘림.