큐와 비동기 API · 큐 설계(순서·중복·유실) · 실습
Redis 리스트와 스트림으로 큐 구현하기
목표
Redis 리스트와 스트림으로 각각 큐를 만들어, 리스트 큐가 메시지를 잃는 지점을 재현하고 스트림의 컨슈머 그룹과 PEL 로 그것을 막는다.
왜 중요한가
LPUSH / BRPOP 두 줄이면 큐가 됩니다. 그래서 많은 팀이 여기서 멈추고, 배포 때마다 처리 중이던 작업이 조용히 사라지는 것을 몇 달 뒤에 발견합니다. BRPOP 이 반환한 순간 메시지는 이미 Redis 에서 지워졌기 때문입니다. 이 실습은 그 유실을 먼저 눈으로 만든 뒤 두 가지 해법을 순서대로 붙입니다. LMOVE 로 처리 중 리스트를 두는 손수 만든 방법과, 처음부터 이 문제를 위해 설계된 스트림의 컨슈머 그룹입니다. 이 둘의 차이를 알면 SQS 의 가시성 타임아웃이나 Kafka 의 오프셋 커밋을 처음 볼 때도 무슨 문제를 푸는 장치인지 바로 보입니다.
단계
1. redis-cli PING 결과를 /root/q/ping.txt 에 저장한다. PONG 이 들어 있어야 한다.
2. /root/q/produce.py 로 q:jobs 에 job-1 부터 job-5 까지 5건을 넣는다. LLEN q:jobs 가 5 이다.
3. /root/q/consume.py 로 5건을 모두 꺼내 /root/q/order.out 에 한 줄씩 적는다. 첫 줄이 job-1, 마지막 줄이 job-5 여야 한다.
4. /root/q/safe_consume.py 는 LMOVE 로 q:jobs 에서 q:jobs:processing 으로 원자적으로 옮긴 뒤 처리하고, 성공하면 처리 중 리스트에서 지운다. 처리 도중 죽는 경우를 흉내 내어 q:jobs:processing 에 1건이 남게 만든다.
5. /root/q/stream_add.py 로 스트림 q:orders 에 5건을 XADD 한다. MAXLEN ~ 1000 상한을 건다. XLEN q:orders 가 5 이다.
6. 컨슈머 그룹 g1 을 만들고 /root/q/stream_consume.py 로 5건을 읽어 그중 4건만 XACK 한다.
7. XPENDING q:orders g1 결과를 /root/q/pending.txt 에 저장한다. 미확인 메시지가 정확히 1건이어야 한다.
8. /root/q/compare.md 에 마크다운 표를 쓴다. 첫 열의 행 제목은 소비 후 보존, 다중 소비자 그룹, 실패 회수, 메모리 네 개이고 리스트와 스트림 열이 있어야 한다.
참고
LMOVE q:jobs q:jobs:processing RIGHT LEFT는 꺼내기와 옮기기를 한 번에 합니다.- 그룹 생성:
XGROUP CREATE q:orders g1 0 - 미확인 조회:
XPENDING q:orders g1 - 흔한 실수 1:
LPUSH로 넣고LPOP으로 꺼내는 것 — 그러면 LIFO 가 됩니다. - 흔한 실수 2: 스트림에
MAXLEN을 안 걸어 메모리가 무한히 자라는 것.
단계 8개
- Redis 연결 확인하기
- 리스트로 큐에 넣기
- 소비 순서가 FIFO 임을 증명하기
- 유실을 막는 처리중 리스트 도입하기
- 스트림에 메시지 추가하기
- 컨슈머 그룹으로 소비하고 확인응답 보내기
- 확인응답 없는 메시지 확인하기
- 리스트와 스트림 비교표 쓰기